余秀华 《我爱你》

巴巴地活着,每天打水,煮饭,按时吃药

阳光好的时候就把自己放进去,像放一块陈皮

茶叶轮换着喝:菊花,茉莉,玫瑰,柠檬

这些美好的事物仿佛把我往春天的路上带

所以我一次次按住内心的雪

它们过于洁白过于接近春天

在干净的院子里读你的诗歌。

这人间情事

恍惚如突然飞过的麻雀儿

而光阴皎洁。

我不适宜肝肠寸断

如果给你寄一本书,我不会寄给你诗歌

我要给你一本关于植物,关于庄稼的

告诉你稻子和稗子的区别

告诉你一棵稗子提心吊胆的春天

C#中串口通信编程 怎么改才能收发汉字

通常情况下,我们的程序代码是如下这样:
SerialPort serialPort = new SerialPort(); //这是串口通信对象
serialPort.WriteLine("Hello World"); //这是发送数据
string message = serialPort.ReadLine(); //这是接收数据
这没什么问题,收发都正常。但当你把英文"Hello World"换成中文"你好世界" 发送出去后,在另一边接收到的却是乱码。那么,怎样正确收发中文呢?正确的做法是,先将中文字符编码后再发送。接收时,将数据解码还原成中文。具体看下面:

//在发送数据时,使用这样的方法代码。
                    System.Text.UTF8Encoding utf8 = new System.Text.UTF8Encoding();
                    Byte[] writeBytes = utf8.GetBytes("你好世界");
                    serialPort.Write(writeBytes, 0, writeBytes.Length);

//在接收数据时,使用这样的方法代码。
                    System.Text.UTF8Encoding utf8 = new System.Text.UTF8Encoding();
                    Byte[] readBytes = new Byte[serialPort.BytesToRead];
                    serialPort.Read(readBytes, 0, readBytes.Length);
                    String decodedString = utf8.GetString(readBytes);
                    Console.WriteLine(decodedString); //控制台输入了中文:你好世界

这样就可以支持收发中文、英文、以其世界上常见的语种文字,毕竟UTF8编码支持大多数语言文字。如果,我们不使用UTF8编码,而使用GB2312编码,可以不可以呢?当然可以了。问题是死的,而头脑是灵活。呵呵~

c#中,确保数据接收完整的 串口处理程序

SerialPort 方法

https://msdn.microsoft.com/zh-tw/library/system.io.ports.serialport.getportnames.aspx

C# 串口通信总结

http://www.cnblogs.com/binfire/archive/2011/10/08/2201973.html

如果一些厂家比较懒的话,没有提供相应的dll,我们只能对它进行串口通信编程了。以前从没接触过串口编程,最近在一个项目中有几个地方都需要采用串口通信,跟公司一个老手请教后,感觉学到了很多东西,特在此做个总结:

 

一:首先我们来认识下什么是串口:

我们可以看到该串口的属性,在C#中我们使用SerialPort类来表示串口

二:串口调试工具:

在对串口进行编程时候,我们要向串口发送指令,然后我们解析串口返回的指令。在这里向大家推荐一款工具。

串口调试助手.exe

复制代码
void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{

//接收数据
string str = "";
do
{
int count = serialPort.BytesToRead;
if (count <= 0)
break;
byte[] readBuffer = new byte[count];

Application.DoEvents();
serialPort.Read(readBuffer, 0, count);
str += System.Text.Encoding.Default.GetString(readBuffer);

} while (serialPort.BytesToRead > 0);
listBox1.Items.Add(str);
}
复制代码

 

 

C# 编写的串口通信程序

http://www.cnblogs.com/zhaoming510/p/3965061.html

如果,翻看我之前的博客,会找到一篇用I/O模拟IIC通信的程序文章。好吧,如果找不到可以点击这里,这里就不在赘述了,系统也已经完全调试通过了。

今天的任务是,把测试得到的数据在上位机的界面上显示出来,于是键盘手花了两天的时间模仿着巨人的肩膀通过了用C#编写的界面程序,界面很简单就像下面显示的一样。

 

c#串口系列文章:

http://bbs.csdn.net/topics/350038794

第一个 串口打开的时候你是要TRY CATCH的 不然会异常
第二个 尽量使用READ和WRITE不要用WRITELINE和READLINE,后两个是要读换行符的
第三个 查看你的串口初始化是否初始化好了

 

为什么我按发送界面就卡住了啊,
程序就不往下执行了。
为什么啊?
没弄明白,请高手指点

如果你只知道编写同步操作代码,就不可能做出专业的产品,而只是知道.net有那些语句而已。

C# 串口操作系列(1) — 入门篇,一个标准的,简陋的串口例子。

http://blog.csdn.net/wuyazhe/article/details/5598945

C# 串口操作系列(2) — 入门篇,为什么我的串口程序在关闭串口时候会死锁 ?

http://blog.csdn.net/wuyazhe/article/details/5606276

C# 串口操作系列(3) — 协议篇,二进制协议数据解析

http://blog.csdn.net/wuyazhe/article/details/5627253

C# 串口操作系列(4) — 协议篇,文本协议数据解析

http://blog.csdn.net/wuyazhe/article/details/5657188

C# 串口操作系列(5)–通讯库雏形

http://blog.csdn.net/wuyazhe/article/details/5797673

GSM Modem

一直在csdn上回关于gsm modem方面的AT指令问题,之前花了不少时间,想想还是补上整理后的内容

http://blog.nonocast.cn/post/4082

在C#中使用SerialPort类实现串口通信 遇到多线程问题

http://bbs.21ic.com/blog-357347-66360.html

 

复制代码
4...添加数据接收的事件

private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)

使用中的一些常见问题

C#中SerialPort类中DataReceived事件GUI实时处理方法(来自wanglei_wan@yahoo.com.cn 的看法)
MSDN:从 SerialPort 对象接收数据时,将在辅助线程上引发 DataReceived 事件。由于此事件在辅助线程而非主线程上引发,因此尝试修改主线程中的一些元素(如 UI 元素)时会引发线程异常。如果有必要修改主 Form 或 Control 中的元素,必须使用 Invoke 回发更改请求,这将在正确的线程上执行.进而要想将辅助线程中所读到的数据显示到主线程的Form控件上时,只有通过Invoke方法来实现 
下面是代码实例: 
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
   int SDateTemp = this.serialPort1.ReadByte();
   //读取串口中一个字节的数据
   this.tB_ReceiveDate.Invoke(   
//在拥有此控件的基础窗口句柄的线程上执行委托Invoke(Delegate)
//即在textBox_ReceiveDate控件的父窗口form中执行委托.
new MethodInvoker(            
/*表示一个委托,该委托可执行托管代码中声明为 void 且不接受任何参数的任何方法。 在对控件的 Invoke 方法进行调用时或需要一个简单委托又不想自己定义时可以使用该委托。*/
delegate{                 
    /*匿名方法,C#2.0的新功能,这是一种允许程序员将一段完整代码区块当成参数传递的程序代码编写技术,通过此种方法可 以直接使用委托来设计事件响应程序以下就是你要在主线程上实现的功能但是有一点要注意,这里不适宜处理过多的方法,因为C#消息机制是消息流水线响应机制,如果这里在主线程上处理语句的时间过长会导致主UI线程阻塞,停止响应或响应不顺畅,这时你的主form界面会延迟或卡死      */                   
this.tB_ReceiveDate.AppendText(SDateTemp.ToString());//输出到主窗口文本控件
this.tB_ReceiveDate.Text += " ";}
    )
    );
}

如何知道当前电脑有哪个串口

在窗体上添加一个comboBox控件。

然后使用comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames()); 或者

string[] portList = System.IO.Ports.SerialPort.GetPortNames();
            for (int i = 0; i < portList.Length; ++i)
            {
                string name = portList;
                comboBox1.Items.Add(name);
            }
复制代码

例子讲解与源码:

Serial Communication using C# and Whidbey

http://www.codeproject.com/Articles/8605/Serial-Communication-using-C-and-Whidbey

[转载]C#中串口通信编程:总结了 串口类,

http://blog.pfan.cn/sword2008/38218.html

 

复制代码
C#:
1)         添加引用
     using System.IO.Ports;
2)         定义SerialPort类实例
      private SerialPort SpCom2 = new SpCom ("COM2", 9600,Parity.None, 8, StopBits.One);
3)         设置通讯端口号及波特率、数据位、停止位和校验位。
        SpCom.PortName = "COM1";
        SpCom.BaudRate = 9600;
        SpCom.Parity = IO.Ports.Parity.None;
        SpCom.DataBits = 8;
        SpCom.StopBits = IO.Ports.StopBits.One;
        或是定义时直接初始化
         private SerialPort SpCom2 = new SpCom ("COM2", 9600,Parity.None, 8, StopBits.One);
4)         发送数据
     SpCom.Write(TextSendData.Text);
5)         添加接受事件
a)        在运行时将事件与事件处理程序相关联(通过委托实现)
SpCom.DataReceived += new SerialDataReceivedEventHandler(SpCom2_DataReceived);
说明:
SerialDataReceivedEventHandler 委托 表示将处理 SerialPort 对象的 DataReceived 事件的方法
b)        添加事件处理程序(签名一定要一致)
              private void SpCom_DataReceived(object sender, SerialDataReceivedEventArgs e)
6)         读取数据
        string data = SpCom .ReadExisting();
复制代码

 

c#中,确保数据接收完整的 串口处理程序:

复制代码
    private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {

            /*
             int n = comm.BytesToRead;//先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致  
            byte[] buf = new byte[n];//声明一个临时数组存储当前来的串口数据  
            received_count += n;//增加接收计数  
            comm.Read(buf, 0, n);//读取缓冲数据  
            builder.Clear();//清除字符串构造器的内容  
            //因为要访问ui资源,所以需要使用invoke方式同步ui。  
            this.Invoke((EventHandler)(delegate  
            {  
                //判断是否是显示为16禁止  
                if (checkBoxHexView.Checked)  
                {  
                    //依次的拼接出16进制字符串  
                    foreach (byte b in buf)  
                    {  
                        builder.Append(b.ToString("X2") + " ");  
                    }  
                }  
                else  
                {  
                    //直接按ASCII规则转换成字符串  
                    builder.Append(Encoding.ASCII.GetString(buf));  
                }  
                //追加的形式添加到文本框末端,并滚动到最后。  
             * 
             * //要解析字符串,根据长度定位,根据特殊字符定位,来解析数据。(还是要确保一个完整的数据包接收完整,并处理完整)
             * 
                this.txGet.AppendText(builder.ToString());  
                //修改接收计数  
                labelGetCount.Text = "Get:" + received_count.ToString();  
            }));  
             */

            int n = serialPort1.BytesToRead;//待读字节个数
            byte[] buf = new byte[n];//创建n个字节的缓存
            serialPort1.Read(buf, 0, n);//读到在数据存储到buf

            //1.缓存数据
            buffer.AddRange(buf);//不断地将接收到的数据加入到buffer链表中
            //2.完整性判断
            while (buffer.Count >= 4) //至少包含帧头(2字节)、长度(1字节)、功能位(1字节);根据设计不同而不同
            {
                //2.1 查找数据头
                if (buffer[0] == 0x0AA) //传输数据有帧头,用于判断. 找到帧头  AA AA 0A 
                {
                    int len = buffer[2];
                    //int len = 79;
                    if (buffer.Count < len + 4) //数据区尚未接收完整,
                    {
                        break;//跳出接收函数后之后继续接收数据
                    }
                    //得到完整的数据,复制到ReceiveBytes中进行校验
                    buffer.CopyTo(0, ReceiveBytes, 0, len + 4);//
                    byte jiaoyan; //开始校验
                    jiaoyan = 0x01;//jiaoyan = this.JY(ReceiveBytes);

                    if (jiaoyan != ReceiveBytes[3]) //验证功能位失败    if (jiaoyan != ReceiveBytes[len+3])
                    {
                        buffer.RemoveRange(0, len + 4);//从链表中移除接收到的校验失败的数据,
                        //MessageBox.Show("数据包不正确!");//显示数据包不正确,
                        continue;//继续执行while循环程序,
                    }
                    buffer.RemoveRange(0, len + 4);
                    //执行其他代码,对数据进行处理。
                    //解析5 6, 7 8字节的经纬度.
                    DataProgress();
                }
                else //帧头不正确时,记得清除
                {
                    buffer.RemoveAt(0);//清除第一个字节,继续检测下一个。
                }
            }
        }
复制代码

 

对串口而言,不存在完整数据长度,都是以Byte为单位;一般来说,通常是透过时间跟固定数量来进行接收动作。(可能要看各PC的OS或Driver的设置情况)

通常在串口处理上,要确认接收数据完整,是在PC软件上进行接收、保存跟判断的动作,在完整收到後,才进行显示或处理。



我现在的处理方法是根据串口接收事件的实际效果来的:接收的数量到达了一个数量后,我才开始处理,这样比原来的简单的判断效果好多了。

复制代码
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
int byteNumber = SerialPort.BytesToRead;

Common.Delay(20);


//延时等待数据接收完毕。
while ((byteNumber < SerialPort.BytesToRead) && (SerialPort.BytesToRead < 4800))
{
byteNumber = SerialPort.BytesToRead;
Common.Delay(20);
}


int n = SerialPort.BytesToRead; //记录下缓冲区的字节个数 
byte[] buf = new byte[n]; //声明一个临时数组存储当前来的串口数据 
SerialPort.Read(buf, 0, n); //读取缓冲数据到buf中,同时将这串数据从缓冲区移除 


//设置文字显示
Control.CheckForIllegalCrossThreadCalls = false;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++)
{
string s;
if (buf[i] < 16)
s = "0" + Convert.ToString(buf[i], 16).ToUpper() + " ";
else
s = Convert.ToString(buf[i], 16).ToUpper() + " ";


sb.Append(s);
}
textBox1.Text = sb.ToString();


}
catch (Exception ee)
{
}
}
复制代码

 

俄罗斯水手 C# 条码批量打印

我们在做条码打印的时候往往因为速度的原因,建议使用批量打印,即将要打印的文档一起提交 而不是一个个的去提交到打印机,这样可以很好的提高打印的效率。

像下图的打印方式如果在打印数量很多的情况下会让你抓狂

所以我们可以使用下面的方式来解决。

其实实现的代码很简单:原文首发在:http://www.ywrj.net/a/NET/CSharp/20120903/11667.html

如下所示:

复制代码
private int currentPageIndex = 0; 
private int rowCount=0; 
private int pageCount=0; 

private void printDocument_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e) 
{ 
    pageCount = 3     //定义页数

    if (currentPageIndex == 0)   //当为第一页时
    {
          //第一页内容
    }
    else if (currentPageIndex == 1)   //当为第二页时
    {
         //第二页内容
    }
    else if (currentPageIndex == 2)   //当为第三页时
    {
         //第三页内容
    }

    currentPageIndex++;      //自动累加
    if (currentPageIndex < pageCount)
    {
        //关键的就是在这里,他会告诉打印机,先别急着打印,后面还有页面,再等等...
        e.HasMorePages = true;  //如果小于定义页 那么增加新的页数
    }
    else
    {
        e.HasMorePages = false; //停止增加新的页数,没页面了,可以打印了
        currentPageIndex = 0;
    }
}
复制代码
分类: C#

WPF打印票据

WPF打印票据或者是打印普通纸张区别不大,只是说打印票据要把需要打的内容摆放好位置,搞定缩放比例,就可以放入票据直接打印了。
那么关键点就是3个:
1、使用WPF提供的什么类、什么方法来执行打印
2、如何摆放位置
3、如何搞定缩放比例

1、使用WPF提供的什么类、什么方法来执行打印

这个问题很容易解决,搜索下WPF打印或WPF Print,就能找到示例代码。
那么我用的是PrintDialog的PrintVisual方法。PrintDialog从名字中可以看出是个对话框,让用户手动选择打印机。如果不想弹出对话框和选择打印机,则可以读取默认打印机或者在配置文件里配置打印机名称,然后找到它。这就需要用到另外的两个类:PrintQueue和LocalPrintServer。
使用PrintDialog打印:

var printDialog = new PrintDialog();
printDialog.PrintQueue = GetPrinter();
printDialog.PrintVisual(visual, visual.Name);

获取打印机任务队列:

public static PrintQueue GetPrinter(string printerName = null)
{
	try
	{
		PrintQueue selectedPrinter = null;
		if (!string.IsNullOrEmpty(printerName))
		{
			var printers = new LocalPrintServer().GetPrintQueues();
			selectedPrinter = printers.FirstOrDefault(p => p.Name == printerName);
		}
		else
		{
			selectedPrinter = LocalPrintServer.GetDefaultPrintQueue();
		}
		return selectedPrinter;
	}
	catch
	{
		return null;
	}
}

2、如何摆放位置

注意到我们上面的打印代码是使用的PrintVisual,参数是Visual,那么这个Visual是什么?
我举个WPF Grid类的继承关系:Grid : Panel : FrameworkElement : UIElement : Visual,所以WPF的控件都是继承自UIElement的,也是继承Visual的。
那么我们把Grid看作是一张票据或一张纸,在这张纸上布置好需要打印的内容,不就OK了吗。
你可以创建一个用户控件来鼠标拖拽摆放,传入实体对象绑定值,也可以动态生成一个Grid。

3、如何搞定缩放比例

仅仅摆放好,打印出来未必是我们想要的结果。因为票据的大小不同,特别是银行那种身份证或金额的小格子,打歪了只能说明技术不到家啊。
所以摆放是要有依据的,依据就是扫描票据,然后在扫描的底图上摆放,样位置就不会错位。然后缩放就是DPI(DPI是Dots Per Inch(每英寸所打印的点数)的缩写)的概念。我们扫描的图是像素的,而实际的纸张不能用像素这个单位。这个之间的换算需要依赖DPI。
具体缩放的方法:

//注意,我这里DPI写死的是150,实际中你的DPI是多少要看扫描件怎么扫的。
var settings = new PrintSettings { Width = visual.Width, Height = visual.Height, DPI = 150 };
var renderTarget = new RenderTargetBitmap((int)settings.Width, (int)settings.Height, settings.DPI, settings.DPI, PixelFormats.Default);
printDialog.PrintTicket = new PrintTicket();
printDialog.PrintTicket.PageMediaSize = new PageMediaSize(renderTarget.Width, renderTarget.Height);
var capabilities = printDialog.PrintQueue.GetPrintCapabilities(printDialog.PrintTicket);
var scale = Math.Max(capabilities.PageImageableArea.ExtentWidth / visual.Width, capabilities.PageImageableArea.ExtentHeight / visual.Height);
visual.LayoutTransform = new ScaleTransform(scale, scale);
var sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
visual.Measure(sz);
visual.Arrange(new Rect(new Point(0, 0), sz));

这样我们就达到了缩放的目的,你可以查看MSDN看看具体的类和方法的含义。

其他的需求:

1、竖打

有些单据比较窄,但是宽度还可以,所以希望可以竖着打印,满足这个需求也是一句话的事情。
在visual.Measure(sz);语句之前增加下面两行代码即可。

printDialog.PrintTicket.PageOrientation = PageOrientation.Landscape;
printDialog.PrintTicket.PageMediaSize = new PageMediaSize(renderTarget.Height, renderTarget.Width);

2、退纸(针式打印机)

退纸并不是常用的功能,但是放错了纸张想拿出来也要费一番力气,所以想让打印机自动吐出纸来。我也搜索了很多问答和文章,也没试出来一个成功的,可能是方法不正确。最终采用了一个比较鸡贼的办法,就是打印一个空白页,然后自动退纸。每种针式打印机可能不同,所以退纸的空白页的大小要调整好。

var printer = GetPrinter();
var visual = new Grid()
{
Width = 1000,
Height = 1500,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left
};
PrintVisual(printer, visual);

3、监控打印任务状态

打印的时候肯定想知道任务有没有被打印,提醒用户放入纸张,打印完毕后提醒用户打印完成。我这里写了一个PrintJobChecker类,Start后就会根据timer的间隔时间检查任务队列,和打印时间。
但是.NET提供的方法并不能很好的做到理想的效果,只能获取到任务还有没有,这是很郁闷的事情。一旦打印机开始打印(注意还没完成),job就是null了。这无法判断纸张是不是还在打印中。如果有朋友知道怎么处理还望评论告知。

public class PrintJobChecker
{
	private DispatcherTimer _timer;
	private PrintQueue _printer;

	private Action<string> _checkingAction;

	public DateTime? StartPrintTime { get; set; }

	private int _interval = 100;
	public int TimerInterval
	{
		get { return _interval; }
		set
		{
			_interval = value;
			_timer.Interval = TimeSpan.FromMilliseconds(value);
		}
	}

	public PrintJobChecker(PrintQueue printer, Action<string> checkingAction)
	{
		if (printer == null || checkingAction == null)
		{
			return;
		}

		_printer = printer;
		_checkingAction = checkingAction;

		_timer = new DispatcherTimer
		{
			Interval = TimeSpan.FromMilliseconds(TimerInterval),
		};

		_timer.Tick += CheckJobStatus;

		PrintingStatus = "正在打印";
		PrintErrorStatus = "打印出错";
		PrintOfflineStatus = "请连接打印机";
		PrintWaittingStatus = "请放入相应的表单至打印机";
		PrintUnknownStatus = "未知错误";
	}

	public void Start()
	{
		_timer.Start();
	}

	public void Stop()
	{
		_timer.Stop();
	}

	private void CheckJobStatus(object sender, EventArgs e)
	{
		if (_printer == null)
		{
			return;
		}

		var job = _printer.GetLastJob();
		if (job == null)
		{
			if (!StartPrintTime.HasValue)
			{
				StartPrintTime = DateTime.Now;
			}
			_checkingAction(PrintingStatus);
		}
		else
		{
			var statusText = GetJobStatus(job);
			_checkingAction(statusText);
		}
	}

	public string PrintingStatus { get; set; }

	public string PrintErrorStatus { get; set; }

	public string PrintOfflineStatus { get; set; }

	public string PrintWaittingStatus { get; set; }

	public string PrintUnknownStatus { get; set; }

	private string GetJobStatus(PrintSystemJobInfo job)
	{
		if (job == null) return null;

		if (((job.JobStatus & PrintJobStatus.Completed) == PrintJobStatus.Completed)
			   ||
			   ((job.JobStatus & PrintJobStatus.Printed) == PrintJobStatus.Printed))
		{
			StartPrintTime = DateTime.Now;
			return PrintingStatus;
		}
		if ((job.JobStatus & PrintJobStatus.Error) == PrintJobStatus.Error)
		{
			_timer.Stop();
			return PrintErrorStatus;
		}
		if ((job.JobStatus & PrintJobStatus.Offline) == PrintJobStatus.Offline
			||
		   job.JobStatus == PrintJobStatus.None)
		{
			return PrintOfflineStatus;
		}
		if ((job.JobStatus & PrintJobStatus.Printing) == PrintJobStatus.Printing)
		{
			if (job.TimeSinceStartedPrinting > 0)
			{
				return PrintingStatus;
			}
			else
			{
				return PrintWaittingStatus;
			}
		}
		return PrintUnknownStatus;
	}
}

剧透预警!《权力的游戏》第七季所有剧情被泄

这不是演习!这不是演习!这不是演习!

 

 

 

 

 

 

 

 

 

据报道,《权力的游戏》第七季所有剧情都已经在网上被泄露了。

据赫芬顿邮报报道,在Reddit网站上,一个注册名为awayforthelads的用户发布了一份巨长巨详细的剧透详单。

本来这份详单可能不值一提,仅仅被当成了粉丝自己的臆测之作而已……直到可靠的《权力的游戏》新闻网站“长城守望”通过知情人士和剧组照片开始证实详单中所说的一些剧情细节是真的……

然后这个用户在Reddit网站的账号就突然注销了。

《权力的游戏》第七季所有剧情都已经在网上泄露。

相比于之前第五季在网上泄露时闹得满城风雨的态度,HBO对此次剧透一事一直保持着一种非同寻常的沉默。

如果此次剧透是真的,那么它就预先警告了大家,新剧情中将会出现一个孕妇,一个同盟,一些深受喜爱的角色回归,几大家族的结局,当然少不了众多角色领便当,以及一个会让粉丝们惊掉下巴的大反叛。

等看完这张图你就没法对接下来的内容视而不见啦。

剧透显示,为了对抗白鬼,瑟曦、龙妈和琼恩之间将会诞生一个不稳定的联盟。

尽管琼恩拒绝向龙妈称臣,但是他同意放弃他北境之王的头衔以交换龙妈帮助他对抗来自白鬼的威胁。

接下来发生的将会是整个剧集最受期待的场面之一:瑟曦、提利昂、琼恩、丹妮莉丝、乔拉、瓦里斯、弥桑黛、科本、葛雷乔伊、魔山以及猎狗,这些角色将共同聚集在君临城的龙洞,在这里琼恩向他们展示了一个尸妖,证明七大王国必须要联合起来共同抵抗夜王。

很显然,琼恩·雪诺将会和龙妈联合起来对付夜王。
与此同时,提利昂开始担心龙妈的脾气。龙妈因为一些维斯特洛领主拒绝臣服于她,就让龙把他们烧死了。

 

艾莉亚想要杀死所有弗雷家的人,小指头则试图挑拨她和姐姐珊莎之间的关系。 

瑟曦惊骇之余同意助雪诺一臂之力,但是当轮到她将她的军队派遣到北境去时,她却食言了,暗自希望白鬼和她的对手可以相互削弱对方的实力。正是在这一刻,詹姆对他的妹妹最终心生厌恶,放弃了她,前往北境参与与白鬼的战争。

詹姆最终会因瑟曦背弃与白鬼作战的诺言与她反目。
乔拉在山姆的帮助下最终治愈了灰鳞病。
琼恩带领队伍北上俘获了一只尸妖,以向外界证明,南方也已经危在旦夕了。

瑟曦认为自己已经怀上了詹姆的孩子,但是在剧透中,不久后她便在一片血泊的床上醒来,据推测应该是流产了。

为了控制住尸妖,琼恩建立了一个由野人首领托蒙德、贝里·唐德利恩、猎狗、密尔的索罗斯和乔拉组成的团队,并带领这支队伍前往长城以北。

但是他们在一个岛上的冰湖上被夜王的军队围攻,索罗斯因此被一个北极熊尸妖杀死(意味着唐德利恩只剩最后一条命了)。琼恩·雪诺的叔叔班扬·史塔克也为了保护琼恩而牺牲。

最后,龙妈带着她的三条龙前来救援,团队成员才得以脱身——但是其中一条龙韦赛利昂不幸被夜王杀死。

他随后将龙复活为自己的坐骑,使它喷出蓝色的火焰,并利用它摧毁城墙。

或许最令人惊讶的是——或者说至少是最令人期待的是——本季的剧情走向将会显示,琼恩和丹妮莉丝最终会相遇…并且在第七季剧终时滚床单。

夜王杀死了龙妈的一条龙韦赛利昂,并将它复活为自己的坐骑。
夜王将利用韦赛利昂喷出的蓝色火焰摧毁长城。

詹德利——上一次出现时,他在第三季中从龙石堡逃离——在最后一季,他将在君临城出现,并在那里锻造武器;戴佛斯·席渥斯找到他后笑话他“还在继续划桨”。

娜梅莉亚——艾莉亚·史塔克的冰原狼,自从为了保护主人而被主人赶走之后就未曾再露面——也将在本季再次回到女主人的身边。

兄弟之爱:猎狗将在君临城的一次谈判中遇见他的哥哥魔山。
詹德利——上一次出现时,他在第三季中从龙石堡逃离——在最后一季,他将在君临城出现,并在那里锻造武器;戴佛斯·席渥斯找到他后笑话他“还在继续划桨”。

 

小指头试图利用珊莎在兰尼斯特家族胁迫下写给罗柏·史塔克的信挑拨离间艾莉亚及其姐姐的关系,但是珊莎在弟弟布兰的帮助下看透了小指头的阴谋诡计,并在艾莉亚的帮助下救出了布兰。

 

密尔的索罗斯被一个北极熊尸妖杀死。

艾莉亚在上一季中杀死了弗雷家族族长瓦德·佛雷,她在本季第一桩大事就是戴着瓦德·佛雷的人脸面具,杀死所有弗雷家的人。在将所有的女人送出后,引导众人举杯祝词,借此毒死了所有人。

小指头试图利用珊莎在兰尼斯特家族胁迫下写给罗柏·史塔克的信挑拨离间艾莉亚及其姐姐的关系,但是珊莎在弟弟布兰的帮助下看透了小指头的阴谋诡计,并在艾莉亚的帮助下救出了布兰。

另一个回归的角色则是乔拉,他在山姆的帮助下治愈了灰鳞病,山姆发现了一位旧镇的老学士的口述,这位老学士曾经治愈了自己的灰鳞病。山姆和布兰还发现了琼恩的真实身世,以及他的真名叫做伊耿。与此同时,吉莉偶然发现雷加·坦格利安的婚姻是无效的,并且他重新和莱安娜·史塔克结婚,从而证明了琼恩登上王座是名正言顺的事情。关于那个婚礼的一段回忆也将会出现。

早些时候,兰尼斯特的军队讨伐了多恩和高庭,消灭了马泰尔和提利尔家族的残支。奥莲娜夫人向詹姆承认是自己毒死了乔佛里,詹姆最终允许她服毒自杀。

在返程的途中,丹妮莉丝伏击了部分兰尼斯特的军队——尽管军队拥有科本设计的专门对付龙的大炮,但还是被龙妈打败了。

瑟曦怀上了詹姆的孩子,但是很可能最后还是流产了。
奥莲娜夫人向詹姆承认是自己毒死了乔佛里,詹姆最终允许她服毒自杀。

 

山姆发现了琼恩的真实身世,吉莉偶然发现雷加·坦格利安的婚姻是无效的,并且他重新和莱安娜·史塔克结婚,从而证明了琼恩登上王座是名正言顺的。
多恩被消灭,沙蛇女被攸伦·葛雷乔伊杀死

与此同时,提利昂开始担心龙妈的脾气。龙妈因为一些维斯特洛领主拒绝臣服于她,就让龙把他们烧死了,其中包括山姆的父亲兰德尔。

至于铁民,攸伦·葛雷乔伊摧毁了阿莎·葛雷乔伊的舰队,囚禁了她,席恩·葛雷乔伊侥幸逃脱。在和瑟曦结盟后,攸伦还杀死了沙蛇的两个人并且抓住了艾拉莉亚·沙德。

但是在被尸妖吓得半死后,攸伦得知尸妖不能游泳,就立即返回了派克岛。

席恩最后得见了琼恩·雪诺,当初雪诺因为他救了珊莎而放了他一命。

《权力的游戏》第七季的将于2017年年中回归,此次回归会比以往都要晚一些。和之前的剧集不同,最终季将只有七集而非十集。

WPF:将HTML RGB颜色值转化为Color对象的两种方式

(1)方式一:

1
Color color1 = (Color)System.Windows.Media.ColorConverter.ConvertFromString("#E0E0E0");

(2)方式二:

1
Color color2 = ConvertToColor("#E0E0E0");

 

1
2
3
4
5
6
7
8
9
10
11
12
public static System.Windows.Media.Color ConvertToColor(string value)
{
       int r = 0, g = 0, b = 0;
       if (value.StartsWith("#"))
       {
            int v = Convert.ToInt32(value.Substring(1), 16);
            r = (v >> 16) & 255; g = (v >> 8) & 255; b = v & 255;
       }
       return System.Windows.Media.Color.FromRgb(Convert.ToByte(r), Convert.ToByte(g), Convert.ToByte(b));
}
记住:如果是将RGB(128,24,34)转换为十六进制,可以分别将数字转换为十六进制:
1
2
3
4
5
6
7
8
9
public string toHex(int digit)
{
    string hexDigit = digit.ToString("X");
    if (hexDigit.Length == 1)
    {
        hexDigit = "0" + hexDigit;
    }
    return hexDigit;
}

然后拼接得到十六进制:

1
string colorCode = "#" +toHex(Color.R) +toHex(Color.G) +toHex(Color.B);

 

(3)方法三

复制代码
        private static Color CreateColorFromString(string s)
        {
            if (string.Compare(s, "None") == 0)
            {
                return Colors.Transparent;
            }
            s = s.Replace("#", "");
            byte result = 0;
            byte num2 = 0;
            byte num3 = 0;
            byte num4 = 0;
            byte.TryParse(s.Substring(0, 2), NumberStyles.HexNumber, (IFormatProvider)null, out result);
            byte.TryParse(s.Substring(2, 2), NumberStyles.HexNumber, (IFormatProvider)null, out num2);
            byte.TryParse(s.Substring(4, 2), NumberStyles.HexNumber, (IFormatProvider)null, out num3);
            byte.TryParse(s.Substring(6, 2), NumberStyles.HexNumber, (IFormatProvider)null, out num4);
            return System.Windows.Media.Color.FromArgb(result, num2, num3, num4);
        }
复制代码

解决C#操作注册表权限不够的问题

在Win7中,系统安全系数已经很高了,因此.NET4.0中对于注册表操作这种高级权限的东西也限制的比较紧,因此,在编程中经常会发现,使用RegistryKey类进行操作时的各种失效或者各种报错。

如何解决这问题呢?

首先,因为要操作注册表,所以,需要程序以管理员身份运行。在Win7系统中的表现即为运行程序时,弹出用户帐户控制对话框,申请以管理员身份运行。

在项目中新建一个后缀为manifest的文件,如下图所示,

创建成功后,这个文件中默认就会产生以下内容

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <asmv1:assembly manifestVersion=“1.0” xmlns=“urn:schemas-microsoft-com:asm.v1” xmlns:asmv1=“urn:schemas-microsoft-com:asm.v1” xmlns:asmv2=“urn:schemas-microsoft-com:asm.v2” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”>
  3.   <assemblyIdentity version=“1.0.0.0” name=“MyApplication.app”/>
  4.   <trustInfo xmlns=“urn:schemas-microsoft-com:asm.v2”>
  5.     <security>
  6.       <requestedPrivileges xmlns=“urn:schemas-microsoft-com:asm.v3”>
  7.         <!– UAC 清单选项
  8.             如果要更改 Windows 用户帐户控制级别,请用以下节点之一替换
  9.             requestedExecutionLevel 节点。
  10.         <requestedExecutionLevel  level=“asInvoker” uiAccess=“false” />
  11.         <requestedExecutionLevel  level=“requireAdministrator” uiAccess=“false” />
  12.         <requestedExecutionLevel  level=“highestAvailable” uiAccess=“false” />
  13.             指定 requestedExecutionLevel 节点将会禁用文件和注册表虚拟化。
  14.             如果要利用文件和注册表虚拟化实现向后
  15.             兼容性,则删除 requestedExecutionLevel 节点。
  16.         —>
  17.         <requestedExecutionLevel  level=“asInvoker” uiAccess=“false” />
  18.       </requestedPrivileges>
  19.     </security>
  20.   </trustInfo>
  21.   <compatibility xmlns=“urn:schemas-microsoft-com:compatibility.v1”>
  22.     <application>
  23.       <!– 此应用程序设计使用的所有 Windows 版本的列表。Windows 将会自动选择最兼容的环境。–>
  24.       <!– 如果应用程序设计使用 Windows 7,请取消注释以下 supportedOS 节点–>
  25.       <!–<supportedOS Id=”{35138b9a-5d96-4fbd-8e2d-a2440225f93a}”/>–>
  26.     </application>
  27.   </compatibility>
  28.   <!– 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) –>
  29.   <!– <dependency>
  30.     <dependentAssembly>
  31.       <assemblyIdentity
  32.           type=“win32”
  33.           name=“Microsoft.Windows.Common-Controls”
  34.           version=“6.0.0.0”
  35.           processorArchitecture=“*”
  36.           publicKeyToken=“6595b64144ccf1df”
  37.           language=“*”
  38.         />
  39.     </dependentAssembly>
  40.   </dependency>>
  41. </asmv1:assembly>

然后按照该xml文件中的提示,把默认的

  1. <requestedExecutionLevel  level=“asInvoker” uiAccess=“false” />

这句代码更换成下面这句代码

  1. <requestedExecutionLevel  level=“requireAdministrator” uiAccess=“false” />

这样程序在运行的是否,便会自动申请管理员权限!注:如上更改后,每次调试程序的是否,也必须将VS以管理员身份运行,这点好蛋疼

但是,仅仅按照如上做还是不够的,这样程序在运行时,如果仅仅是读取注册表一般不会有问题,可是一旦有写注册表或者删除注册表等操作时,有时候还是会出错!

这个问题出在RegistryKey这个类中的OpenSubKey函数上

OpenSubKey函数有3个重载,一般用的时候,都是使用的一个参数这个重载,如下

[csharp] view plain copy

 print?

  1. RegistryKey aimdir = software.OpenSubKey(“Run”);

在仅仅使用一个重载项打开注册表,一般只能进行读操作,对于写操作会报错崩溃为了达到写操作的目的,需要使用两个参数的这个重载,并把第二个参数设置为true,如下所示:

[csharp] view plain copy

 print?

  1. string RegeditPath = @“SOFTWARE\Microsoft\Windows\CurrentVersion\”;
  2. RegistryKey hkml = Registry.LocalMachine;
  3. RegistryKey software = hkml.OpenSubKey(RegeditPath, true);
  4. RegistryKey aimdir = software.OpenSubKey(“Run”,true);
  5. aimdir.SetValue(“Pngye_Warehouse”, Application.ExecutablePath);

上面的一段代码,就可以成功的设置程序的开机自动运行!进行了一次注册表写操作!

0

WPF 单实例方法

WPF 中,在Application.Run()运行后且Window.Show()运行前会激发Application.Startup事件,在Application类里有OnStartup()是Application.Startup事件的Handler,可以重载OnStartup(),在OnStartup()里面控制应用实例。

[csharp] view plain copy

  1. public partial class App : Application
  2. {
  3.     protected override void OnStartup(StartupEventArgs e)
  4.     {
  5.         bool isCanCreateNew;
  6.         System.Threading.Mutex mutex = new System.Threading.Mutex(true“OnlyRunOneInstance”out isCanCreateNew);
  7.         if (!isCanCreateNew)
  8.         {
  9.             MessageBox.Show(“已经启动!”);
  10.             this.Shutdown(1);
  11.         }
  12.         Console.WriteLine(“OnStartup”);
  13.         base.OnStartup(e);
  14.     }
  15. }

或者订阅Application.Startup事件

[csharp] view plain copy

  1. <Application x:Class=“WpfStudy.App”
  2.              xmlns=“http://schemas.microsoft.com/winfx/2006/xaml/presentation”
  3.              xmlns:x=“http://schemas.microsoft.com/winfx/2006/xaml”
  4.              Startup=“Application_Startup”
  5.              StartupUri=“MainWindow.xaml”>
  6.     <Application.Resources>
  7. </Application.Resources>
[csharp] view plain copy

  1. public partial class App : Application
  2.    private void Application_Startup(object sender, StartupEventArgs e)
  3.    {
  4.        bool isCanCreateNew;
  5.        System.Threading.Mutex mutex = new System.Threading.Mutex(true“OnlyRunOneInstance”out isCanCreateNew);
  6.        if (!isCanCreateNew)
  7.        {
  8.            MessageBox.Show(“已经启动!”);
  9.            this.Shutdown(1);
  10.        }
  11.        Console.WriteLine(“OnStartup”);
  12.    }

WPF系列之应用程序生命周期

摘要:

WPF是微软最新的图形用户界面技术,从2003年公之于众(当时开发代号Avalon),其革命性的创建软件的方式便引起了高度关注,特别是对于使用Windows Form和GDI开发的人员。时至今日使用WPF进行开发已经不是什么新鲜事,但我还是想写一点关于WPF的东西与不太了解WPF的朋友一起学习。OK,今天就从最基础的开始吧,我们一块看一下WPF应用程序的生命周期。

内容:

1.一个简单的WPF应用

2.WPF中的主窗体

3.Application的生命周期

4.单实例运行WPF应用

一、一个简单的WPF应用

WPF应用程序是一种包含Application对象的Windows进程,Application对象提供了生命周期服务,因此要了解WPF应用的生命周期我们就需要从Application开始。

首先我们建立一个WPF应用,在默认情况下我们运行这个应用程序。

我们使用默认WPF Application创建了一个WPF应用,默认情况下我们什么都不做,点击运行就会看到上面的窗口。那么这背后Visual Studio为我们做了什么呢?我们知道在Winform中有一个Program.cs,其中定义了Main函数,程序从Main开始执行,那么WPF有没有类似的函数呢?我们的MainWindow又是在何处指定运行的?

我们可以看到VS为我们自动生成了一个App.xaml及其对应的隐藏文件App.xaml.cs。在App.xaml.cs中我们可以看到它没有创建任何类,更没有启动MainWindow,那么打开App.xaml呢?打开App.xaml文件代码如下:

复制代码
1 <Application x:Class="WPFLifeCycle.App"
2              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4              StartupUri="MainWindow.xaml" ShutdownMode="OnLastWindowClose">
5 <Application.Resources>
6          
7 </Application.Resources>
8 </Application>
复制代码

 

在这里我们看到x:Class=”WPFLifeCycle.App”,事实上这样的代码等同于创建了一个名为App的Application对象。而根节点Application的StartupUri属性指定了启动的窗口(StartupUri=”MainWindow.xaml”),这就相当于创建了一个MainWindow类型的对象,然后调用其Show()方法。此时可能会有朋友问,按理说这个程序应该有个Main函数啊,为何在此看不到Main呢?程序如何创建Application?

其实,这一切都归功于App.xaml文件的一个属性BuildAction

BuildAction属性指定了程序生成的方式,默认为ApplicationDefinition。对于WPF程序来说,如果指定了BuildAction为ApplicationDefinition之后,WPF会自动创建 Main函数,并且自动检测Application定义文件,根据定义文件自动创建Application对象并启动它(当然它会根据StartupUri创建MainWindow并显示)。

既然如此我们将BuildAction设置为None试试,我们运行会发现抛出如下错误:

很明显这时我们需要Main函数作为我们的程序入口,我们不妨手动创建试试,此时我们创建一个Program.cs类,代码如下:

复制代码
 1 using System;
 2 using System.Windows;
 3 
 4 namespace WPFLifeCycle
 5 {
 6 staticclass Program
 7     {
 8         [STAThread]
 9 staticvoid Main()
10         {
11             Application App =new Application();
12             MainWindow mw =new MainWindow();
13             mw.Show();
14             App.Run();
15         }
16     }
17 }
复制代码

运行之后我们看到的效果和之前完全一样。换句话说App.xaml文件和我们上面代码起到的效果是相同的,事实上上面的xaml代码在编译时编译器也会做出同样的解析,这也是WPF设计的一个优点–很多东西我们都可以在XAML中实现而不需要编写过多的代码。

备注: App.xaml帮我们做的工作具体如下:

a.创建Application对象,并且设置其静态属性Current为当前对象

b.根据StartupUri创建并显示UI

c.设置Application的MainWindow属性(主窗口)

d.调用Application对象的Run方法,并保持一直运行直到应用关闭

二、WPF中的主窗口

我们知道在Winform中我们有”主窗体”概念,在WPF中我们也同样有”主窗口”。”主窗口”是一个”顶级窗口”,它不包含或者不从属于其他窗口。默认情况下,创建了Application对象之后会设置Application对象的MainWindow属性为第一个窗口对象来作为程序的”主窗口”。当然,如果你愿意这个属性在程序运行的任何时刻都是可以修改的。

在Winform中我们知道,主窗体关闭之后整个应用程序生命周期就会结束,这里我们不妨试试在WPF中是否如此。首先在应用程中添加另一个Window对象OtherWindow,然后在MainWindow中放一个按钮,点击按钮显示OtherWindow。运行效果如下:

现在点击关闭MainWindow之后我们发现OtherWindow并未关闭,当然Application并未结束:

这是不是说明Application关闭同Winform不同呢(当然我们调用Application.Current.Exit()是可以退出应用的)?在WPF中Application的关闭模式同Winform确实不同,WPF中应用程序的关闭模式有三种,它由Application对象的ShutdownMode属性来决定

它的枚举值如下:

枚举名称 枚举值 说明
OnLastWindowClose 0 当应用程序最后一个窗口关闭后则整个应用结束
OnMainWindowClose 1 当主窗口关闭后则应用程序结束
OnExplicitShutdown 2 只用通过调用Application.Current.Shutdown()才能结束应用程序

从上图我们也可以看到默认情况下ShutdownMode值是OnLastWindowClose,因此当MainWindow关闭后应用程序没有退出,如果要修改它可以将光标放到App.xaml中的XAML编辑窗口中,然后修改属性窗口中的ShutdownMode,也可以在XAML中或者程序中设置ShutdownMode属性。

三、Application的生命周期

下面我们看看Application的生命周期(引用网上一张图片)

上图片描述WPF应用的生命周期,其中值得一提的是Run方法后会调用应用程的Starup事件,而”已激活”、”已停用”分别对应Activated和Deactivate事件。DispatcherUnhandledException用来将事件路由到正确位置的对象,包括未处理的异常,可以用它来处理程序其他部分未处理的异常或者一些操作(例如保存当前文档)。当关闭、注销或者重新启动时则会触发SessionEnding事件,SessionEnding事件中的SessionEndingCancelEventArgs的ReasonSessionEnding属性可以指示你是执行了注销还是关闭(这是一个枚举属性)。

四、单实例运行WPF应用

虽然上面我们简单介绍了WPF应用的生命周期,但是默认情况下我们可以打开一个应用程序多个实例,例如你双击一个exe多次。当然有些时候这么做会带来很多好处,但是有时我们又不希望这么做,要避免这个问题其实很简单,同WinForm中单实例运行一个应用是一样的,我们只需要在应用程序启动时创建一个”排他锁”,修改App.xaml.cs如下:

复制代码
 1 using System;
 2 using System.Windows;
 3 using System.Threading;
 4 
 5 namespace WPFLifeCycle
 6 {
 7 ///<summary>
 8 /// Interaction logic for App.xaml
 9 ///</summary>
10 publicpartialclass App : Application
11     {
12         Mutex mutex=null;
13 protectedoverridevoid OnStartup(StartupEventArgs e)
14         {
15 base.OnStartup(e);
16 bool createdNew =false;
17             mutex =new Mutex(true, "WPFLifeCycle",out createdNew);
18 if (!createdNew)
19             {
20                 MessageBox.Show("程序正在运行中,无法启动另一个实例!", "系统提示", MessageBoxButton.OK, MessageBoxImage.Warning);
21 this.Shutdown();
22             }
23         }
24     }
25 }
复制代码

此时如果我们已经运行了WPFLifeCycle.exe,当再双击此应用则会给出提示:

C#实现限制软件的使用次数

实例说明 
为了使软件能被更广泛的推广,开发商希望能有更多的用户使用软件,但他们又不想让用户长时间免费使用未经授权的软件,这时就可以推出试用版软件,限制用户的使用次数,当用户感觉使用方便的话,可以花钱获取注册码,以获取其正式版软件。本实例使用C#实现了限制软件使用次数功能,运行本实例,如果程序未注册,则提示用户已经使用过几次,如图1所示,然后进入程序主窗体,单击主窗体中的“注册”按钮,弹出如图2所示的软件注册窗体,该窗体中自动获取机器码,用户输入正确的注册码之后,单击“注册”按钮,即可成功注册程序,注册之后的程序将不再提示软件试用次数。
\
图1 使用次数提示
\
图2 软件注册
注册码可由光盘中程序文件夹下提供的注册机程序得到。
设计思路 
限制软件的使用次数时,首先需要判断软件是否已经注册,如果已经注册,则用户可以任意使用软件。如果软件未注册,则判断软件是否初次使用,如果是初次使用,则在系统注册表中新建一个子项,用来存储软件的使用次数,并且设置初始值为1;如果不是初次使用,则从存储软件使用次数的注册表项中获取已经使用的次数,然后将获取的使用次数加一,作为新的软件使用次数,存储到注册表中。
技术要点 
本实例获取软件使用次数时用到Registry类的GetValue方法,向注册表中写入软件使用次数时用到Registry类的SetValue方法。另外,在对软件进行注册时,需要根据硬盘序列号和CPU序列号生成机器码和注册码,此时用到WMI管理对象中的ManagementClass类、ManagementObject类和ManagementObjectCollection类,下面对本实例中用到的关键技术进行详细讲解。
(1)Registry类的GetValue方法
Registry类提供表示Windows注册表中的根项的RegistryKey对象,并提供访问项/值对的静态方法,其GetValue方法用来检索与指定的注册表项中的指定名称关联的值,如果在指定的项中未找到该名称,则返回提供的默认值;如果指定的项不存在,则返回null。GetValue方法语法格式如下:
public static Object GetValue(string keyName,string valueName,Object defaultValue)
? keyName:以有效注册表根(如“HKEY_CURRENT_USER”)开头键的完整注册表路径。
? valueName:名称/值对的名称。
? defaultValue:当name不存在时返回的值。
? 返回值:如果由keyName指定的子项不存在,则返回null;否则,返回与valueName关联的值;或者,如果未找到valueName,则返回defaultValue。
例如,下面代码用来获取软件的使用次数:
tLong = (Int32)Registry.GetValue(“HKEY_LOCAL_MACHINE/SOFTWARE/tryTimes”, “UseTimes”, 0);
Registry类位于Microsoft.Win32命名空间下。
(2)Registry类的SetValue方法
Registry类的SetValue方法用来设置注册表项中的名称/值对的值,该方法为可重载方法,它有两种重载形式,第一种重载形式语法格式如下:
public static void SetValue(string keyName,string valueName,Object value)
? keyName:以有效注册表根(如“HKEY_CURRENT_USER”)开头键的完整注册表路径。
? valueName:名称/值对的名称。
? value:要存储的值。
第二种重载形式语法格式如下:
public static void SetValue(string keyName,string valueName,Object value,RegistryValueKind valueKind)
? keyName:以有效注册表根(如“HKEY_CURRENT_USER”)开头键的完整注册表路径。
? valueName:名称/值对的名称。
? value:要存储的值。
? valueKind:存储数据时使用的注册表数据类型。
例如,本实例中将软件使用次数写入注册表的实现代码如下:

if (tLong < 30)
{
int Times = tLong + 1;
Registry.SetValue("HKEY_LOCAL_MACHINE/SOFTWARE/tryTimes", "UseTimes", Times);
}

(3)ManagementClass类
ManagementClass类表示公共信息模型(CIM)管理类。管理类是一个WMI类,如Win32_LogicalDisk类和Win32_Process类,前者表示磁盘驱动器,后者表示进程(如Notepad.exe)。通过该类的成员,可以使用特定的WMI类路径访问WMI数据。
例如,本实例中使用ManagementClass类对本地进程信息进行访问,代码如下:
ManagementClass myCpu = new ManagementClass(“win32_Processor”);
(4)ManagementObject类
ManagementObject类表示WMI实例,本实例中用到该类的Get方法、GetPropertyValue方法和Properties属性,其中Get方法用来将WMI类信息绑定到管理对象,其语法格式如下:
public void Get()
GetPropertyValue方法用来获取某属性值的等效访问器,其语法格式如下:
public Object GetPropertyValue(string propertyName)
? propertyName:相关的属性的名称。
? 返回值:指定的属性的值。
例如,本实例中获取硬盘序列号的代码如下:
ManagementObject disk = new ManagementObject(“win32_logicaldisk.deviceid=”d:””);
disk.Get();
return disk.GetPropertyValue(“VolumeSerialNumber”).ToString();
Properties属性用来获取描述管理对象属性的PropertyData对象的集合,其语法格式如下:
public virtual PropertyDataCollection Properties { get; }
? 属性值:返回一个PropertyDataCollection,它包含管理对象的属性。
例如,本实例中获取CPU序列号的代码如下:

foreach (ManagementObject myObject in myCpuConnection)
{
strCpu = myObject.Properties["Processorid"].Value.ToString();
break;
}

(5)ManagementObjectCollection类
ManagementObjectCollection类表示通过WMI检索到的管理对象的不同集合,此集合中的对象为从ManagementBaseObject派生的类型,包括ManagementObject和ManagementClass。
例如,本实例中通过使用ManagementClass对象的GetInstances方法获取管理对象集合,代码如下:
ManagementObjectCollection myCpuConnection = myCpu.GetInstances();
ManagementClass类、ManagementObject类和ManagementObjectCollection类都位于System.Management命名空间下,添加该命名空间时,首先需要在“添加引用”中添加System.Management.dll组件
实现过程 
(1)新建一个Windows应用程序,将其命名为LimitSoftUseTimes,将其默认窗体重命名为frmMain。
(2)在 LimitSoftUseTimes项目中添加一个Windows窗体,命名为frmRegister,用来实现软件注册功能。frmRegister窗体主要用到的控件及说明如表1所示。
表1frmRegister窗体主要用到的控件及说明
\
(3)主要程序代码。
frmMain窗体加载时,首先判断程序是否注册,如果已经注册,则将主窗体Text属性设置为“主窗体(已注册)”,否则,将主窗体Text属性设置为“主窗体(未注册)”,并且提示软件为试用版和已经使用的次数,同时将注册表中记录的软件使用次数加一。frmMain窗体的Load事件代码如下:

private void frmMain_Load(object sender, EventArgs e)
{
RegistryKey retkey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("software", true).CreateSubKey("mrwxk").CreateSubKey("mrwxk.ini");//打开注册表项
foreach (string strRNum in retkey.GetSubKeyNames())//判断是否注册
{
if (strRNum == softreg.getRNum())//判断注册码是否相同
{
this.Text = "主窗体(已注册)";
button1.Enabled = false;
return;
}
}
this.Text = "主窗体(未注册)";
button1.Enabled = true;
MessageBox.Show("您现在使用的是试用版,该软件可以免费试用30次!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
Int32 tLong;
try
{
//获取软件的已经使用次数
tLong = (Int32)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\tryTimes", "UseTimes", 0);
MessageBox.Show("感谢您已使用了" + tLong + "次", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch
{
//首次使用软件
Registry.SetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\tryTimes", "UseTimes", 0, RegistryValueKind.DWord);
MessageBox.Show("欢迎新用户使用本软件", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
//获取软件已经使用次数
tLong = (Int32)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\tryTimes", "UseTimes", 0);
if (tLong < 30)
{
int Times = tLong + 1;//计算软件本次是第几次使用
//将软件使用次数写入注册表
Registry.SetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\tryTimes", "UseTimes", Times);
}
else
{
MessageBox.Show("试用次数已到", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
Application.Exit();//退出应用程序
}
}
上面的代码中用到getRNum方法,该方法用来根据指定的机器码生成注册码,实现代码如下:
//生成注册码
public string getRNum()
{
setIntCode();//初始化127位数组
for (int i = 1; i < Charcode.Length; i++)//把机器码存入数组中
{
Charcode[i] = Convert.ToChar(this.getMNum().Substring(i - 1, 1));
}
for (int j = 1; j < intNumber.Length; j++)//把字符的ASCII值存入一个整数组中。
{
intNumber[j] = intCode[Convert.ToInt32(Charcode[j])] + Convert.ToInt32(Charcode[j]);
}
string strAsciiName = "";//用于存储注册码
for (int j = 1; j < intNumber.Length; j++)
{
if (intNumber[j] >= 48 && intNumber[j] <= 57)//判断字符ASCII值是否0-9之间
{
strAsciiName += Convert.ToChar(intNumber[j]).ToString();
}
else if (intNumber[j] >= 65 && intNumber[j] <= 90)//判断字符ASCII值是否A-Z之间
{
strAsciiName += Convert.ToChar(intNumber[j]).ToString();
}
else if (intNumber[j] >= 97 && intNumber[j] <= 122)//判断字符ASCII值是否a-z之间
{
strAsciiName += Convert.ToChar(intNumber[j]).ToString();
}
else//判断字符ASCII值不在以上范围内
{
if (intNumber[j] > 122)//判断字符ASCII值是否大于z
{
strAsciiName += Convert.ToChar(intNumber[j] - 10).ToString();
}
else
{
strAsciiName += Convert.ToChar(intNumber[j] - 9).ToString();
}
}
}
return strAsciiName;//返回生成的注册码
}
在frmMain窗体中单击“注册”按钮时,弹出“软件注册”窗体,该窗体加载时自动获得本机机器码,实现代码如下:
private void frmRegister_Load(object sender, EventArgs e)
{
textBox1.Text = softreg.getMNum();
}

上面的代码中用到getMNum方法,该方法为自定义的返回值类型为string类型的方法,它主要用来根据本机的CPU和硬盘序列号生成本机机器码。getMNum方法实现代码如下:

//生成机器码
public string getMNum()
{
string strNum = getCpu() + GetDiskVolumeSerialNumber();//获得24位Cpu和硬盘序列号
string strMNum = strNum.Substring(0, 24);//从生成的字符串中取出前24个字符做为机器码
return strMNum;//返回生成的机器码
}
getMNum方法中用到getCpu和GetDiskVolumeSerialNumber两个自定义方法,其中getCpu方法用来获取本机CPU序列号,GetDiskVolumeSerialNumber方法用来获取本机硬盘序列号,它们的实现代码如下:
//获得CPU的序列号
public string getCpu()
{
string strCpu = null;
ManagementClass myCpu = new ManagementClass("win32_Processor");//获取系统CPU处理器
ManagementObjectCollection myCpuConnection = myCpu.GetInstances();
foreach (ManagementObject myObject in myCpuConnection)
{
strCpu = myObject.Properties["Processorid"].Value.ToString();//获取CPU序列号
break;
}
return strCpu;
}
// 取得设备硬盘的序列号
public string GetDiskVolumeSerialNumber()
{
//获取系统硬盘
ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
ManagementObject disk = new ManagementObject("win32_logicaldisk.deviceid="d:"");
disk.Get();
return disk.GetPropertyValue("VolumeSerialNumber").ToString();//返回硬盘序列号
}

frmRegister窗体中单击“注册”按钮时,程序首先判断是否输入注册码,如果没有,弹出信息提示,否则判断用户输入的注册码与本机应该生成的注册码是否相同,如果相同,则注册成功,并将软件注册信息写入到注册表中。“注册”按钮的Click事件代码如下:

private void button1_Click(object sender, EventArgs e)
{
if (textBox2.Text == "")//判断是否输入了注册码
{
MessageBox.Show("注册码输入不能为空!", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
if (textBox2.Text.Equals(softreg.getRNum()))//判断注册码是否正确
{
RegistryKey retkey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("software", true).CreateSubKey("mrwxk").CreateSubKey("mrwxk.ini").CreateSubKey(textBox2.Text);//打开注册表项,并创建一个子项
retkey.SetValue("UserName", "mrsoft");//为新创建的注册表项设置值
MessageBox.Show("注册成功,程序需要重新加载!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
this.Hide();//隐藏当前窗体
frmMain frmmain = new frmMain()//实例化主窗体对象;
frmmain.Show();//显示主窗体
}
else
{
MessageBox.Show("注册码输入错误!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}

C#中Trim()、TrimStart()、TrimEnd()的用法

C#中Trim()、TrimStart()、TrimEnd()的用法:
这三个方法用于删除字符串头尾出现的某些字符。Trim()删除字符串头部及尾部出现的空格,删除的过程为从外到内,直到碰到一个非空格的字符为止,所以不管前后有多少个连续的空格都会被删除掉。TrimStart()只删除字符串的头部的空格。TrimEnd()只删除字符串尾部的空格。

如果这三个函数带上字符型数组的参数,则是删除字符型数组中出现的任意字符。如Trim(“abcd”.ToCharArray())就是删除字符串头部及尾部出现的a或b或c或d字符,删除的过程直到碰到一个既不是a也不是b也不是c也不是d的字符才结束。
这里最容易引起的误会就是以为删除的是”abcd”字符串。如下例:
string s = ” from dual union all “;
s = s.Trim().TrimEnd(“union all”.ToCharArray());
可能有人以为上面s的最终结果是”from dual”,但真正的结果是”from d”。需要注意的是这种写法执行的删除对象是字符数组中出现的任意字符,而不是这些字符连在一起组成的字符串!

一般TRIM函数用法:
  Trim()   功能删除字符串首部和尾部的空格。   语法Trim ( string )   参数string:string类型,指定要删除首部和尾部空格的字符串返回值String。函数执行成功时返回删除了string字符串首部和尾部空格的字符串,发生错误时返回空字符串(””)。如果任何参数的值为NULL,Trim()函数返回NULL。   ========================================================================   SQL 中的 TRIM 函数是用来移除掉一个字串中的字头或字尾。最常见的用途是移除字首或字尾的空白。这个函数在不同的资料库中有不同的名称:   MySQL: TRIM(), RTRIM(), LTRIM()   Oracle: RTRIM(), LTRIM()   SQL Server: RTRIM(), LTRIM()   各种 trim 函数的语法如下:   TRIM([[位置] [要移除的字串] FROM ] 字串): [位置] 的可能值为 LEADING (起头), TRAILING (结尾), or BOTH (起头及结尾)。 这个函数将把 [要移除的字串] 从字串的起头、结尾,或是起头及结尾移除。如果我们没有列出 [要移除的字串] 是什么的话,那空白就会被移除。   LTRIM(字串): 将所有字串起头的空白移除。   RTRIM(字串): 将所有字串结尾的空白移除。

WPF自适应可关闭的TabControl 类似浏览器的标签页

效果如图:

 

虽然说是自适应可关闭的TabControl,但TabControl并不需要改动,不如叫自适应可关闭的TabItem.

大体思路:建一个用户控件,继承自TabItem,里面放个按钮,点击的时候在TabControl中移除自身.在添加,移除TabItem和TabControl尺寸变化时,通过Items的个数计算合适的Width.

新建用户控件

新建用户控件,并继承自TabItem,这样它就拥有TabItem所有的属性和事件.而这个功能不需要自定义依赖属性和事件.它的用法就和TabItem完全一样.

建完后把UserControl换成TabItem,去掉多余部分

后台继承自UserControl改成继承自TabItem

更改样式添加关闭按钮

在Xmal里添加一个自己喜欢的样式,最主要的是在Template里添加一个按钮,注册一个Click事件,用于关闭.

复制代码
 1 <Style TargetType="{x:Type TabItem}">
 2             <Setter Property="BorderBrush" Value="Black"></Setter>
 3             <Setter Property="Background" Value="White"></Setter>
 4             <Setter Property="Foreground" Value="Black"></Setter>
 5             <Setter Property="Padding" Value="5,0,0,0"></Setter>
 6             <Setter Property="HorizontalAlignment" Value="Left"></Setter>
 7             <Setter Property="VerticalAlignment" Value="Center"></Setter>
 8             <Setter Property="HorizontalContentAlignment" Value="Left"></Setter>
 9             <Setter Property="VerticalContentAlignment" Value="Center"></Setter>
10             <Setter Property="Template">
11                 <Setter.Value>
12                     <ControlTemplate TargetType="{x:Type TabItem}">
13                         <Border CornerRadius="5,0,0,0" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
14                             <Grid>
15                                 <Grid.ColumnDefinitions>
16                                     <ColumnDefinition Width="*"></ColumnDefinition>
17                                     <ColumnDefinition Width="20"></ColumnDefinition>
18                                 </Grid.ColumnDefinitions>
19                                 <ContentPresenter Grid.Column="0" ContentSource="Header" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"></ContentPresenter>
20                                 <Button Grid.Column="1" Name="btn_Close" Click="btn_Close_Click"></Button>
21                             </Grid>
22                         </Border>
23                         <ControlTemplate.Triggers>
24                             <Trigger Property="IsSelected" Value="true">
25                                 <Setter Property="Background" Value="#FFFF923E"></Setter>
26                                 <Setter Property="Foreground" Value="White"></Setter>
27                             </Trigger>
28                         </ControlTemplate.Triggers>
29                     </ControlTemplate>
30                 </Setter.Value>
31             </Setter>
32         </Style>
复制代码

 

后台的逻辑

查找父级TabControl

注意TabItem并不能关闭自身,这里所说的关闭其实是在他父级TabControl的Items集合里移除.而且父级TabControl的尺寸改变时还要注册事件去改变每个Item的Width.所以我决定找到它的父级TabControl,声明一个私有变量添加对父级的引用.

可以通过可视化树的帮助类VisualTreeHelper来找到它的父级TabControl.当然并不是它的父级直接就是TabControl了,需要递归去查找

复制代码
 1 /// <summary>
 2         /// 递归找父级TabControl
 3         /// </summary>
 4         /// <param name="reference">依赖对象</param>
 5         /// <returns>TabControl</returns>
 6         private TabControl FindParentTabControl(DependencyObject reference)
 7         {
 8             DependencyObject dObj = VisualTreeHelper.GetParent(reference);
 9             if (dObj == null)
10                 return null;
11             if (dObj.GetType() == typeof(TabControl))
12                 return dObj as TabControl;
13             else
14                 return FindParentTabControl(dObj);
15         }
复制代码

 

计算尺寸

既然是自适应,总得有一个正常的尺寸,只有空间不足的时候才去缩小每个Item.我想到的最简单的办法就是做个约定,把这个尺寸放到父级TabControl的Tag里,这样可以通过对父级TabControl的引用,轻松拿到这个尺寸.

计算方法就是取父级TabControl运行时的宽度ActualWidth除以约定的尺寸,取整形int,这个就是保持约定宽度item个数的临界值了.

小于等于这个值就用约定宽度,大于这个值就用父级运行宽度除以Items的个数求出平均宽度,然后遍历父级TabControl的Items,都赋上这个平均值.

需要注意的是,如果所有Items的尺寸加起来大于等于父级的尺寸,Items会换行,感觉有点丑啊.所以我取的是父级运行宽度-5做的运算,这样就永远也抵达不到边界,不会换行.

不过也可以改写TabControl的控件模版,把放Hrader的容器换成Stackpanel就不会换行了,我只是觉得上面的方法比较简单.

父级尺寸改变

可以通过TabControl的SizeChanged事件监测到.需要干的事就是重新计算尺寸.

关闭按钮

在父级TabControl的Items集合里移除自身后,注意重新计算下尺寸和移除注册SizeChanged事件的方法.

最后附上代码 自适应可关闭的Tab.zip

这个效果比较常见,可能您已经做过了,有更好的想法希望您能分享出来,大家共同进步.

 

2016-08-16更新:感谢园友 日日夜夜 的反馈,源码已改正

1.TabItem.Resources的关闭按钮样式添加了Key,模版里的关闭按钮添加了对资源的引用.

2.去掉了TabItem样式的HorizontalContentAlignment=”Left”,VerticalContentAlignment=”Center”.头部内容的布局方式改为HorizontalAlignment=”{TemplateBinding HorizontalAlignment}” VerticalAlignment=”{TemplateBinding VerticalAlignment}”.

WPF之数据绑定总结

最近几天高强度开发,暴露出不少问题,WPF还达不到信手拈来的地步,好些东西还要去看看以前的项目。平时还是要多总结的,层次高了之后关注的知识点才会更深入。下面总结下WPF的绑定相关,总结之前又看了一遍深入浅出WPF,结合平时用到的得出此文(以TextBox为例,覆盖常见的需求,其他控件类似,代码下载,先看代码再看解释效果更好)。
本文主要包含以下内容:
1.TextBox绑定后台的值(一次绑定,类似于赋值);
2.TextBox绑定后台的值(可通过改绑定的值自动更新值);
3.TextBox绑定另一个控件的属性值(随时更新值);
4.TextBox绑定另一个控件的属性值(双向更新);
5.TextBox绑定资源的值;
6.GridView选择一行显示其信息;
7.其他一些注意点

       1.TextBox绑定后台的值(一次绑定,类似于赋值);

        前台设计页面:
[html] view plain copy

  1. <Label>tbDataFirst:</Label>
  2. <TextBlock Name=“tbDataFirst” Width=“120” TextAlignment=“Center”
  3.             Text=“{Binding BindData}”></TextBlock>

后台代码:

[csharp] view plain copy

  1. private string _BindData = string.Empty;
  2. public string BindData
  3. {
  4.     get
  5.     {
  6.         if (_BindData.Length == 0)
  7.             _BindData = “this is BindData”;
  8.         return _BindData;
  9.     }
  10.     set
  11.     {
  12.         _BindData = value;
  13.     }
  14. }

初始化时:tbDataFirst.DataContext = this;
前台将绑定的逻辑固定,后台给数据源,这里后台绑定的源是当前对象,前台获得当前对象的BindData属性值;

        2.TextBox绑定后台的值(可通过改绑定的值自动更新值);

前台设计页面:

[html] view plain copy

  1. <Label>tbDataSecond:</Label>
  2. <TextBox Name=“tbDataSecond” Width=“120”></TextBox>
  3. <Button Click=“Button_Click”>ChangeTextInfo</Button>

后台代码:

[csharp] view plain copy

  1. private string _TextBoxData = string.Empty;
  2. public string TextBoxData
  3. {
  4.     get
  5.     {
  6.         if (_TextBoxData.Length == 0)
  7.             _TextBoxData = “this is data”;
  8.         return _TextBoxData;
  9.     }
  10.     set
  11.     {
  12.         if (_TextBoxData != value)
  13.         {
  14.             _TextBoxData = value;
  15.             OnPropertyChanged(“TextBoxData”);
  16.         }
  17.     }
  18. }
  19. public event PropertyChangedEventHandler PropertyChanged;
  20. public virtual void OnPropertyChanged(string propertyName)
  21. {
  22.     if (PropertyChanged != null)
  23.     {
  24.         PropertyChanged(thisnew PropertyChangedEventArgs(propertyName));
  25.     }
  26. }
  27. private void Button_Click(object sender, RoutedEventArgs e)
  28. {
  29.     TextBoxData = DateTime.Now.ToString(“HH:mm:ss.fff”);
  30. }

绑定时代码:

[csharp] view plain copy

  1. tbDataSecond.SetBinding(TextBox.TextProperty,
  2.     new Binding(“TextBoxData”) { Source = this, Mode = BindingMode.TwoWay });

这里要注意跟1的区别是这里能自动更新界面的值,要实现这个功能,则源对象须继承INotifyPropertyChanged接口,同时在属性值改变时触发通知事件,即OnPropertyChanged(“TextBoxData”)。这样每次TextBoxData属性值变化时界面将会自动更新相应的值;

        3.TextBox绑定另一个控件的属性值(随时更新值);

        前台设计页面:
[html] view plain copy

  1. <StackPanel Orientation=“Horizontal” Margin=“5”>
  2.     <Label>Input String:</Label>
  3.     <TextBox Name=“tbInput” Width=“120”></TextBox>
  4.     <Label>Output String:</Label>
  5.     <TextBox Name=“tbOutpnput” Width=“120”
  6.                 Text=“{Binding Text, ElementName=tbInput}”></TextBox>
  7. </StackPanel>

这里全部有前端实现,tbOutpnput监控tbInput控件的文本值,tbInput一有变化则tbOutpnput也会跟着变化(但本质是tbInput控件通知tbOutpnput控件的);

        4.TextBox绑定另一个控件的属性值(双向更新);

前台设计页面:

[html] view plain copy

  1. <StackPanel Orientation=“Horizontal” Margin=“5”>
  2.     <Label>TwoWayInput String:</Label>
  3.     <TextBox Name=“tbTwoWayInput” Width=“120”></TextBox>
  4.     <Label>TwoWayOutpnput String:</Label>
  5.     <TextBox Name=“tbTwoWayOutpnput” Width=“120”
  6.                 Text=”{Binding Text, ElementName=tbTwoWayInput,
  7.                 UpdateSourceTrigger=PropertyChanged}”></TextBox>
  8. </StackPanel>
        这里跟3的区别就在于UpdateSourceTrigger属性值设为PropertyChanged,TextBox控件的默认值是LostFocus;

        5.TextBox绑定资源文件的值;

        前台设计页面:
[html] view plain copy

  1. <Window.Resources>
  2.     <sys:String x:Key=“TestInfo”>Hello World!</sys:String>
  3. </Window.Resources>
  4. <StackPanel Orientation=“Horizontal” Margin=“5”>
  5.     <Label>Read from resources:</Label>
  6.     <Label Content=“{StaticResource TestInfo}”></Label>
  7. </StackPanel>

注意这里需要在前台页面的Windows命名空间加上xmlns:sys=”clr-namespace:System;assembly=mscorlib”;

        6.GridView选择一行显示其信息;

        这里内容主要包括:GridView和ComboBox绑定数据源(第一次加载时绑定)、TextBox和ComboBox绑定GridView的选择项的详细信息;
        前台设计页面:
[html] view plain copy

  1. <StackPanel Margin=“5”>
  2.     <ListView x:Name=“lvStudent”>
  3.         <ListView.View>
  4.             <GridView >
  5.                 <GridViewColumn Header=“Name” Width=“100”
  6.                                 DisplayMemberBinding=“{Binding Name}”></GridViewColumn>
  7.                 <GridViewColumn Header=“Level”  Width=“100”
  8.                                 DisplayMemberBinding=“{Binding Level.LevelInfo}”>
  9.                 </GridViewColumn>
  10.             </GridView>
  11.         </ListView.View>
  12.     </ListView>
  13. </StackPanel>
  14. <StackPanel Orientation=“Horizontal” Margin=“5”>
  15.     <Label>Name:</Label>
  16.     <TextBox Name=“tbName” Width=“120”
  17.         Text=”{Binding Path=SelectedItem,
  18.                         ElementName=lvStudent,
  19.                         Converter={StaticResource ItemToName}}”></TextBox>
  20. </StackPanel>
  21. <StackPanel Orientation=“Horizontal” Margin=“5”>
  22.     <Label>LevelInfo</Label>
  23.     <ComboBox Name=“cbLevel” AllowDrop=“False” Width=“180”
  24.                 SelectedIndex=”{Binding Path=SelectedItem,ElementName=lvStudent,
  25.                     Converter={StaticResource ItemToIndex}}”></ComboBox>
  26. </StackPanel>
        绑定数据源:
[csharp] view plain copy

  1. private void InitDataSource()
  2. {
  3.     lvStudent.ItemsSource = DataProvider.StudentList;
  4.     cbLevel.ItemsSource = DataProvider.LevelList;
  5.     cbLevel.DisplayMemberPath = “LevelInfo”;
  6. }

注意这里ComboBox绑定时要设置DisplayMemberPath值;
TextBox和ComboBox绑定GridView的选择项时,由于GridView的选择项是Object的,文本下拉框无法自动获取其数据,需要自定义转换帮助类,继承自IValueConverter,具体写法如下(SelectItemConverter为例):

[csharp] view plain copy

  1. [ValueConversion(typeof(object), typeof(string))]
  2. public class SelectItemConverter : IValueConverter
  3. {
  4.     public object Convert(object value, Type t, object para, CultureInfo culture)
  5.     {
  6.         Student data = value as Student;
  7.         return data != null ? data.Name : string.Empty;
  8.     }
  9.     public object ConvertBack(object value, Type t, object para, CultureInfo culture)
  10.     {
  11.         return null;
  12.     }
  13. }

这里是将选择的项先转换成Student对象然后获取其Name属性数据,注意设置ValueConversion特性,前台页面相应的引入命名空间以及标记转换类(详细请对照源码);

         7.其他一些注意点

7.1前台和后台重复绑定时以后一次的绑定为主(刚开始开始学习时看有的人前台后台都要绑一遍,后来才知道那是重复的);
7.2触发通知事件时注意在值变化之后触发,也就是_TextBoxData = value;OnPropertyChanged(“TextBoxData”);不要写倒了,刚开始学习时也遇过,写反了之后会导致第一次改变值没有反应(嘿嘿,其实当时只是依葫芦画瓢,没太理解,才会犯那些错误);
7.3如果要绑定的集合也自动更新可以使用ObservableCollection代替List,前者实现了INotifyPropertyChanged接口,在集合变化时会自动更新界面;
7.4上面的6其实在有数据驱动的思想,具体各位可以自行学习;

         至此总结完成,感觉讲的不够透彻,深入理解还需自己研究。希望能对初学者有些帮助,如果有什么错误或想法,还望不吝指教,转载请保留原文链接
         源码下载

WPF 中的 loaded 事件和 Initialized 事件

在 WPF 中, 控件有 Loaded 和 Initialized 两种事件. 初始化和加载控件几乎同时发生, 因此这两个事件也几乎同时触发. 但是他们之间有微妙且重要的区别. 这些区别很容易让人误解. 这里介绍我们设计这些事件的背景. (不仅适用于 Control 类, 同样在通用类如 FrameworkElement 和 FrameworkContentElement 类也适用.)

下面是个小故事:

  • Initialized 事件只说: 这个元素已经被构建出来,并且它的属性值都被设置好了,所以通常都是子元素先于父元素触发这个事件.当一个元素的 Initialized 事件被触发, 通常它的子树都已经初始化完成, 但是父元素还未初始化. 这个事件通常是在子树的 Xaml 被加载进来后触发的. 这个事件与 IsInitialized 属性相互绑定.
  • Loaded 事件说: 这个元素不仅被构造并初始化完成,布局也运行完毕,数据也绑上来了,它现在连到了渲染面上(rendering surface),秒秒钟就要被渲染的节奏.到这个时候,就可以通过 Loaded 事件从根元素开始画出整棵树. 这个事件与 IsLoaded 属性绑定.

如果你不确定该用哪个事件, 而且也不想继续读下去, 那就用 Loaded 事件好了, 通常它都是对的.然后, 就是整个故事了.

Initialized 事件

这个事件在所有子元素都被设置完成时触发. 具体来说, FrameworkElement/FrameworkContentElement 实现了 ISupportInitialize 接口, 当该接口的 EndInit  方法调用时, IsInitialized 值被设置为 true. 事件就被触发了.

ISupportInitialize 在 WPF 之前就存在了. 有这个接口, 你就可以在设置 control 的某个属性时,提前告知它你要开始执行一个批处理,之后再告诉它你已经做完了.这样实现了这个接口的对象就可以推迟它的属性值修改事件的处理直到 EndInit 被调用. 在 WPF 中, 不只是 element 用这个接口来触发 Initialized 事件, 其他对象如 DataSourceProvider 也实现这个接口.

槽点是, 到底什么时候调用 EndInit 方法? 起点在 Xaml 加载器.(如果你懂 Baml 的话, 这个方法 Baml 加载器也会调用.)  Xaml 加载器在构造对象时就调用 BeginInit. (也就是看见了起始标签), 然后在结束标签那里调用 EndInit 方法. 例子如下:

[html] view plain copy

  1. <Button Width=“100”>
  2.   Hello
  3. </Button>

…创建一个 Button 对象, 调用 BeginInit, 设置宽度属性, 设置内容属性, 调用 EndInit 方法.如果用代码来构建元素, 你也可以自己调用 BeginInit/EndInit 方法. 有个问题就是, 不用 Xaml 构造, 也不自己调用 EndInit 方法, 那初始化事件还能触发吗? 答案必须是 yes. 所以我们提供了一些别的方式来设置 IsInitialized 值, 触发事件.

  •  当一个未初始化的元素被加到可视化树中时, 初始化事件被触发. 这个方法对于所有的非根元素都有效. 至于根元素, 所有的根元素都是从 PresentationSource 中来的, 所以你懂的…
  • 当一个未初始化元素被加入到 PresentationSource 中的时候, 初始化事件会被近似的触发.

从 Initialized 事件的定义中, 可以看出, 这个事件必定是由下向上触发的, 也就是说父元素不应该被初始化直到子元素被初始化完成. 所以通常情况下都是子元素先于父元素被初始化. 不过这一点无法保证, 因为任何人都有可能调用 ISupportInitialize. 从 Xaml 中加载元素的话, 这点倒是可以保证.

另一个槽点是, 元素要这个事件干嘛用? 元素无法获取别处定义的 styles 直到初始化事件触发. 例如 Button1 会从这个 style 中获取一个蓝色的背景. 但是在初始化事件之前, 这个背景是null.

[html] view plain copy

  1. <Page xmlns=“http://schemas.microsoft.com/winfx/2006/xaml/presentation” >
  2.   <Page.Resources>
  3.     <Style TargetType=“Button”>
  4.       <Setter Property=“Background” Value=“Blue” />
  5.     </Style>
  6.   </Page.Resources>
  7.   <Button Name=“Button1”>Clack</Button>
  8. </Page>

Loaded 事件

Loaded 事件在元素即将要被渲染时触发. 设计这个事件是考虑你可能需要在程序加载期间做一些初始化操作.

用 Initialized 事件也可以满足这个要求, 因为这个事件意味着元素已经被构建出来, 而且它的元素值也被设置过. 但这个事件还是少了点东西. 举个例子, 你可能需要知道一个元素的 AcutualWidth 属性值, 但是初始化事件触发时, 实际宽度还没有计算出来. 或者你想要看数据绑定的值, 这个值一样也还没有设定.

所以, 我们提供了 Loaded 事件. 它可以在窗口渲染完成, 但是还没有执行任何交互时触发. 我们原本以为控件在可以接受输入的时候做加载是初始化操作就够了. 但是当我们开始在加载事件中触发动画时, 我们发现了一个问题. 有那么一小会, 你会发现元素内容在渲染时没有动画效果, 过后你才会看到动画效果. 你可能没有发现这个问题, 但是这个问题在远程运行程序时会很明显.

所以我们移动了这个事件, 保证在这个事件之前数据绑定和布局有充足的事件执行,同时保证在第一次渲染前触发.(注意如果你要在加载事件中做任何使布局失效的操作, 那一定要记得在渲染前重新运行下布局. )

因为整棵元素数在同一时间走到 Loaded 事件,这个事件会在整棵树内广播. 广播从根元素开始, 所以加载事件是从父元素到子元素.

属性是鸡, 事件是蛋.

另一个槽点是, 到底是先改了属性值,然后触发了事件, 还是先触发事件再改属性值.(一般人都知道答案吧, 作者在卖萌.)

在 WPF 中, 如果有一个属性以及一个和该属性相关的事件, 通常都是修改该属性值来触发该事件. 例如对于 ListBox, 总是修改 SelectedItem 属性值, 触发了SelectionChanged 事件,  Loaded 和 Initialized 事件也遵循这个模式.

对于 Loaded 事件有点特殊, 在任何元素的 Loaded 事件触发前, IsLoade 属性在整棵元素树中被设置. 也就是说, 元素树内的所有元素的 IsLoaded 值被设置为 true 之后, 所有元素的 Loaded 事件才被触发.

现在回过头来看上面 Page 中 的 Button 的例子,从 Xaml 文件中加载这个page, 你应当会看到以下的执行顺序.

  • Button.IsInitialized goes true
  • Button.Initialized event is raised
  • Page.IsInitialized goes true
  • Page.Initialized event is raised
  • Page IsLoaded goes to true
  • Button IsLoaded goes to true
  • Page.Loaded is raised
  • Button.Loaded is raised