程序效果
最终得到程序的运行效果如图。拖动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>间插入:
这一段代码主要是将整个界面唰唰切成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的代码看起来应该是这样的:
睁大眼睛啦!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这个类的后面,也就是这个类的}的后面,相当于跟这个类并列的地位,不要搞错了啊,插入一个类,代码如下:
学过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中定义函数
还记得那个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就会跑过来,然后把自己的背景改掉了。大功告成!运行看看: