手把手教你入门WPF
引言
自古中国人便强调教学相长。学生将自己所学到的知识表述出来,在思考”讲什么”的过程中对知识的细节便有更好的把握,在考虑”如何讲”的过程中对知识的框架又有了更好的思考。写这个文(也许是这系列文)的目的便是对最近学的WPF知识做一个回顾与总结。也希望抛砖引玉,激发出更多的思考来。(还有一个不可告人的目的是引蛇出洞,引出一些WPF的爱好者来捏哈哈)
想跟着这篇文做出一个WPF程序来需要你会:使用鼠标,键盘和Visual Studio 2008。
想学会这篇文中提到的WPF知识则要求有面向对象程序设计的基础,.NET语言为佳,以及Windows程序设计的基础。退一步说至少也要有程序设计的基础。
总的来看,这篇文主要讲的是如何用WPF做一个自定义按钮背景色的Windows窗口程序出来。最后会有一些个人的分析。
程序效果
最终得到程序的运行效果如图。拖动Slider可以使按钮的背景色出现相应变化。
需求分析和架构设计
如果是你,接到了这样的一个程序设计要求,会怎样思考?
第一步当然是需求分析啦。这个程序相对简单,需要分析的主要是各个控件之间的数据联系。这主要体现在Slider, Textbox和Button间的同步关系上:
拖动Slider的滑块,要求TextBox里的数值也要变化,同时Button的背景色也要发生相应变化。改变TextBox里的值,滑块的位置和Button的背景也要做相应调整。
一个习惯于WinForm的同学就会有很自然的想法——处理消息(.NET表现为事件)呗。对Slider滑块的滑动这个事件,设置一个EventHandler,以更新Textbox的Text属性和Button的Background属性。对于Textbox.Text的更改这个事件,设置一个EventHandler,更新Slider的Value属性和Button的Background属性。
很好。下面我们就来看一下在WPF中,思维可以变得如此不同而简单。
实现过程
第一步自然是创建工程。首先在Visual Studio 2008下,新建一个工程,选择Visual C#里面的WPF Application即可。
稍等片刻就可以看见一个工程被创建,同时默认的窗体设计文件Windows1.xaml被显示出来了。注意虽然XAML中可以像MFC那样用图形化的方式放置控件,但是由于XAML的功能太强大,图形化界面很难完全表达,因此在目前的版本中主要还是采用代码的方式进行界面设计。不过微软已经放风说在Visual Studio 2010中这样的情况会加以改善。
第二步是设计UI。首先在XAML代码中<Grid></Grid>间插入:
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
这一段代码主要是将整个界面唰唰切成5行3列一共15个大块,在设计界面中可以很清楚的看到(如下图)。其中最左边和最右边这两列固定为50像素,中间那一列是宽度可变的。
下面就要往里面放控件了。在</Grid.ColumnDefinitions>和</Grid>间继续加入以下代码:
<TextBlock Grid.Column="0" Grid.Row="0">Red</TextBlock>
<Slider Grid.Column="1" Grid.Row="0"/>
<TextBox Grid.Column="2" Grid.Row="0"/>
这是在第一行的第一个格子里放了一个TextBlock控件,用来显示那个Red。然后在同一行的第二个格子里放了一个Slider,第三个格子里放了一个TextBox。现在设计界面是这样的:
看起来好丑哦。没关系我们来修饰一下。
首先窗口太大了,把XAML文件中第四行中Window1的Height改成200先。然后每个控件把格子占得满满的,影响美观。在各个控件中加入Margin="5",比如Slider控件的代码现在看起来应该是这样的:
<Slider Grid.Column="1" Grid.Row="0" Margin="5"/>
这样就使得空间和格子间有一定的间距,看起来舒服一点。
然后将各个控件的垂直对齐方式设为中间对齐,加入VerticalAlignment="Center"即可。好的,现在蛮漂亮的了。
代码看起来应该是这样:
<TextBlock Grid.Column="0" Grid.Row="0" Margin="5" VerticalAlignment="Center">Red</TextBlock>
<Slider Grid.Column="1" Grid.Row="0" Margin="5" VerticalAlignment="Center"/>
<TextBox Grid.Column="2" Grid.Row="0" Margin="5" VerticalAlignment="Center"/>
下面我们来加入其余三个TextBlock, Slider和TextBox。分别代表Green, Blue, Alpha值。将以上代码复制粘贴三份即可。注意每一行都要相应修改Grid.Row值。第二行为1,以此类推。不要忘了把后面几行TextBlock里的Red改成相应的Green, Blue等等哦。现在界面看起来像这样:
最后需要在最后一行加入一个Button。在</Grid>前添加如下代码:
<Button Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="4"
HorizontalAlignment="Center" Width="100" Margin="5">
<Rectangle Width="10" Height="10" Fill="Red"/>
</Button>
很容易看懂这是说要添加一个宽度100像素,水平中央对齐,在第5行的按钮。按钮的内容是一个长宽各为10,红色的方形。只是要注意Grid.ColumnSpan="3"表示这一个控件将占用3列的空间。
再把Window1的Title改成ColorChange。至此UI的设计就结束了。如下图。
呼,界面设计说起来还是比较繁琐的。终于进入WPF强大的地方了。首先是界面设计的时候注意到了吗?Button的内容可以是一个方块诶!如果我告诉你还能是其他控件比如是一个slider外加一个TextBox呢?就像这样:
要是在MFC里面还不要搞死人。最简单的情况是通过改CButton的bitmap来实现在按钮上显示图片。就算只想在里面嵌入一个Slider,就首先要继承CButton类,然后广大人民充分发挥聪明才智吧,拼死拼活总算搞出来一种控件叫做CSliderButton。然后一拍脑袋,哎呀我搞错了,其实我想嵌入一个TextBox的。好吧,大返工……但是在WPF里如此简单,我们要做的仅仅是把<Rectangle />换成其他控件就好了,比如<Slider />,或者另一个有复杂结构的<Grid>。是不是很神奇?
还有我们上面进行的UI设计相对MFC来说繁琐很多,但是也要强大很多。这主要表现在窗口的大小被改变时。一个MFC对话框程序,如果没有经过特别精心的设计的话,一旦窗口大小被拖动或者最大化,一坨控件还像原来一样分布,拥堵在左上角,唉……但是上面设计出的WPF的程序就不会,控件仍然会均匀分布在窗口中。(你可以自己试试看,如果觉得还是不够完美可以自己想想怎样再改动呢?)
下面进入消息处理阶段了,如果还可以这样称呼的话。第一步要把Slider和TextBox中的内容关联起来。为了以下程序设计的方便,我们必须要先给各个控件起个名字。在第一行的Slider里面添加Name属性,叫它ColorSliderR好了!也就是说,在<Slider ... />中加入Name="ColorSliderR"
同样的,我们给剩下各个slider起名叫做ColorSliderG/B/A。把那个Button称作MainButton。
然后要调整Slider的属性,否则没有应用价值。在各个Slider的代码里加入Maximum="255" Minimum="0" Value="255"。这表示滑块到达最右边的时候表示255,最左边表示0,初始值在255。那么Slider的代码看起来应该是这样的:
<Slider Grid.Column="1" Grid.Row="0" Margin="5" VerticalAlignment="Center" Maximum="255" Minimum="0" Value="255" Name="ColorSliderR"/>
睁大眼睛啦!WPF神奇的地方又来啦!什么消息处理,这么麻烦,走开!不就是同步嘛,把TextBox和Slider绑上不就得了。好的。在4个<TextBox ... />中加入代码Text="{Binding Path=Value, ElementName=ColorSliderR}",注意ElementName的值要根据各个控件改动哦。这样就把两个控件Bind起来了。运行一下看看:
成功啦!简单不?啧啧,还带小数点的,真牛X。注意,如果想要通过改变文本框的内容来改变滑块位置的话,需要在输入完毕后切换一下焦点,也就是随便点一下本窗口内的其他地方改动才会生效。
真么快?都到第四步了。下面就要改变背景色啦。XAML固然强大,但光玩控件间的绑定没意思,咱辛辛苦苦学的for啊while啊不都没用了嘛。这下咱换种方式。把XAML和后台代码绑定起来。
为了实现这个目标,先要做一个桥梁——在XAML和后台代码间沟通的桥梁。终于轮到C#上场了,人家都急死了。右击XAML设计界面,单击View Code打开Window1.xaml.cs。啊,终于看到熟悉的using了,激动死了激动死了。
在public partial class Window1这个类的后面,也就是这个类的}的后面,相当于跟这个类并列的地位,不要搞错了啊,插入一个类,代码如下:
public class SliderCommunicator: INotifyPropertyChanged
{
// INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
int sliderValue = 255;
public int SliderValue
{
get { return sliderValue; }
set
{
sliderValue = value;
Notify("SliderValue");
}
}
}
学过C#的对这个应该很熟悉,标准的事件处理方法。大意就是这个类里面有一个属性SliderValue。它一旦被更改了就会大叫:老大我被人动啦!然后——当然很多人根本不睬他,只有他的老大听到了就会过来看看并作出相关操作。这是C#针对消息处理作出的语言内部的支持。
还有,不要忘了在最前面那一坨using的最后插入一句
using System.ComponentModel;
这是为了提供对事件处理的支持。
有了这个类,桥架起来了,绑定就好办了。当然一个控件应该是和代码中的一个对象绑定而不是和一个类绑定,所以叫你有面向对象程序设计基础呢,否则对象啊类啊还不把你绕昏掉。对象自然只能在Window1类中定义。好的,在类中(具体位置随便在哪,只要这个类里面就好)加入
SliderCommunicator sliderCR, sliderCG, sliderCB, sliderCA;
然后搞个函数把他们初始化一下,整个Window1类看起来应该是这样的:
public partial class Window1 : Window
{
SliderCommunicator sliderCR, sliderCG, sliderCB, sliderCA;
public Window1()
{
InitializeComponent();
}
void InitDataBinding()
{
sliderCR = new SliderCommunicator();
sliderCG = new SliderCommunicator();
sliderCB = new SliderCommunicator();
sliderCA = new SliderCommunicator();
}
}
嗯嗯,很好,下面我们就可以把XAML和C#绑起来了。两步走,第一步,改改InitDataBinding函数,改成这样:
void InitDataBinding()
{
sliderCR = new SliderCommunicator();
ColorSliderR.DataContext = sliderCR;
sliderCG = new SliderCommunicator();
ColorSliderG.DataContext = sliderCG;
sliderCB = new SliderCommunicator();
ColorSliderB.DataContext = sliderCB;
sliderCA = new SliderCommunicator();
ColorSliderA.DataContext = sliderCA;
}
意思就是把每个Slider控件和相应的对象绑起来。
第二步,回到XAML设计界面,在每一个<Slider ... />里把原来的Value="255"改成Value="{Binding Path=SliderValue}"
这是更进一步具体地说,把这个Slider的Value属性和相应的那个对象的SliderValue绑定起来。
好吧现在看看发生了什么事情呢。当Slider的滑块一被拖动的时候,WPF就会自动更新绑在一起的SliderCommunicator对象的Value属性,然后它就会大叫起来老大我被人动啦!但是现在还没人理他呀,没用呢。没关系,我们给他造出一个老大来。
首先在Window1中定义函数
void ChangeMainButtonColor(object sender, PropertyChangedEventArgs e)
{
SolidColorBrush NewColor = new SolidColorBrush();
NewColor.Color = Color.FromArgb((byte)sliderCA.SliderValue, (byte)sliderCR.SliderValue, (byte)sliderCG.SliderValue, (byte)sliderCB.SliderValue);
MainButton.Background = NewColor;
}
还记得那个Button其实叫做MainButton吗?这个函数就是改变Button背景值的。下面让它做个实实在在的老大。
在InitDataBinding的最后部分加入语句
sliderCR.PropertyChanged += new PropertyChangedEventHandler(ChangeMainButtonColor);
sliderCG.PropertyChanged += new PropertyChangedEventHandler(ChangeMainButtonColor);
sliderCB.PropertyChanged += new PropertyChangedEventHandler(ChangeMainButtonColor);
sliderCA.PropertyChanged += new PropertyChangedEventHandler(ChangeMainButtonColor);
好了。现在SliderCommunicator一叫Button就会跑过来,然后把自己的背景改掉了。大功告成!运行看看:
哇塞,完美呢!鼓掌~撒花~
展望&总结
如果你只想做一个简单的WPF程序看到这里就可以了。如果你感兴趣的话,可以继续想一想,哇塞,WPF的Data Binding相对于MFC等传统的WinForm编程省了多少事,又有多大的应用前景哪!看看QQ, MSN, 酷我音乐盒,他们都有动态改变主题啊色调啊这样的功能。用MFC的话感觉这需要多么大的工作量,现在看看WPF,是不是很简单?WPF能如此简单地改变一个按钮的颜色,当然也可以很方便地改变一批按钮的颜色乃至整个样式。如果感兴趣可以去看看WPF中关于Style和Control Template的内容。
再回头想想,那个又有Slider又有TextBox的Button是不是很别致?当然这只是一个例子没什么直接的价值。但这种模式相对于MFC也是巨大的突破——UI的定制灵活了许多。你甚至可以在Button上放一段小动画,只要你想。如果有兴趣可以去看看WPF中关于Control Template, Animation和Triggers的内容。
抛砖引玉完毕!我很喜爱这门技术。如果你因此对WPF有了兴趣甚至转而学习WPF我很荣幸。如果你是mm我更加欣喜(囧)
posted on 2010-02-07 12:06 grapeot 阅读(11415) 评论(10) 编辑 收藏 举报