用Silverlight打造位运算器(1)--制作简易工具条控件

 

那日,一网友发了一份他做破解计算器源码给我,看到这个程序,我突然想起了我写的第一个程序――位运算器,那是一个半途而废的程序。当时已经写了一千多行的代码,由于初学,里面有大量的重复代码,估计写完得用3000行以上的代码,而且大部是重复代码,这让我很困惑,于是放弃了继续完成它,转而寻找解除困惑的途径。之后,随着学习的深入,虽然这种困惑早已不复存在,但位运算器也消失在我的记忆之中了。网友的程序让我有了完成这个位运算器的念头,或许因为它是我写的第一个程序,在我的内心深处,多少还存留有一些对它的眷顾吧。

说做就做,接下来就要考虑使用什么语言来实现它,用C#WindowApplication来写当然没问题,但那样学不到什么新东西,亏本的买卖不能做。Silverlight之前虽然学过一些,也做过一些东西,但说实话,还属于初学者,正好可以借此机会进一步了解Silverlight

位运算器在我的构思中需要有工具条按钮,就是按下去不会弹上来那种按钮。当年Delphi中有一个SpeedButton控件正好可以实现这样的功能。C#WindowsApplication中可以使用RadioButton来实现这样的工具条。很遗憾,Silverlight中没有相关控件。但可以更改ListBox的模板来模拟这样的功能,这个我试验过,但由于ListBox存在Focus状态,ListItem在处于Focus状态时会有一个边框,看着很不顺眼,需要重写模板把这个状态去掉。定制RadioButton外观应该也能实现这样的功能(这点未做实验,有待验证)。但想来想去,为了学些新东西,还是自己实现一个工具条控件吧。经过一番努力,终于实现了这个工具条。本文将对制作的步骤进行详细讲解。

好,先来看看效果,这里需要注意,必须安装Silverlight 3.0版本才能正常运行示例。

 

关于控件制作,请先观看一篇写得非常棒的文章:

http://msdn.microsoft.com/zh-cn/magazine/cc721611.aspx

由于我的控件放在另一个项目之中,操作步骤会有所不同,所以这里有些地方我还得重新详细地介绍。

第一步,先做一个最简单的控件:

1、 首先创建一个Silverlight应用程序,项目命名为“BitCalculator”。

2、 在解决方案资源管理器的【解决方案“BitCalculator”】上单击鼠标右键,选中【添加】à【新建项目】添加一个【Silverlight类库】,命名为“BitLibrary”。

3、 将【BitLibrary】项目下的“Class1.cs”类改名为“SpeedButton.cs”。

4、 SpeedButton.cs类的代码更改如下:

 

1 namespace BitLibrary
2 {
3         public class SpeedButton : ContentControl
4         {
5 
6         }
7 }
8 

 

这里,我们让SpeedButton类继续自ContentControl,道理很简单,Button类也是从ContentControl类继续的。很多的拥有Content属性的内置控件都继承自ContentControl类。我们记得,在C#中,Button使用的是Text属性,为什么在Silverlight中会改为Content属性呢?这是因为Content属性允许用户对按钮表面的内容进行自定义,如加一些图片或其他控件什么的,而所有这些都不会影响按钮被单击。

 

5、  现在这个最简单的控件已经完成,可以加载到主页面测试一下了。打开BitCalculator】项目下的【MainPage.xaml】文件,首先添加对这个控件的引用:

xmlns:src="clr-namespace:BitLibrary;assembly=BitLibrary"

这里的“src”是一个前缀,也可以把它理解为引用名,你高兴的话可以换成其他的单词。之后所有需要使用到SpeedButton的地方都需要加上“src:”前缀。

(这一段是我介绍的那篇文章未描述步骤)还需要做一件事才能在MainPage.xaml中看到SpeedButton:在【BitCalculator】项目上单击鼠标右键,选中【添加引用】打开“添加引用”窗口,在【项目】栏中选中“BitLibrary”,单击【确定】按钮添加对BitLibrary的引用。

接下来,在MainPage.xaml中加入SpeedButton控件:

<src:SpeedButton/>

完成后,MainPage.xaml的所有代码应该象下面这个样子:

 

<UserControl x:Class="BitCalculator.MainPage"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d
=http://schemas.microsoft.com/expression/blend/2008 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:src
="clr-namespace:BitLibrary;assembly=BitLibrary"
    mc:Ignorable
="d" d:DesignWidth="640" d:DesignHeight="480">
    
<Grid x:Name="LayoutRoot">
        
<src:SpeedButton/>
    
</Grid>
</UserControl>

 

6、运行程序,我们发现浏览器中无任何内容,这很正常,我们的SpeedButton里面什么都没有。

 

第二步,修改控件模板:

我们首先看看使用常规方法是否能让SpeedButton显示一些内容:在MainPage.xaml】中更改SpeedButton的声明如下:

 

<src:SpeedButton Width="30" Height="30" Background="Black"
                         Foreground
="#FF000000" Content="HEX"
                         BorderBrush
="Black" BorderThickness="2"/>

 

运行程序,我们发现除了Content里的“HEX“这几个字母外,其他的背景及边框根本没有按照我们的设想显示出来。这时,可以通过创建默认控件模板指定控件外观。

1、             BitLibrary】项目上单击鼠标右键,选择【添加】à【新建文件夹】添加一个新的文件夹,并把文件夹命名为“themes”。

2、             在【themes】文件夹上单击鼠标右键,选择【添加】à【新建项】,新建一个文本文件,并命名为:“generic.xaml”。这里需要注意,文件夹和文件的名称都不能搞错。

3、             打开generic.xaml文件,并填入如下代码:

 

<ResourceDictionary     xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:src
="clr-namespace:BitLibrary;assembly=BitLibrary"
    xmlns:vsm
="clr-namespace:System.Windows;assembly=System.Windows">
    
<!-- SpeedButton Template -->
    
<Style TargetType="src:SpeedButton">
        
<Setter Property="Width" Value="30"/>
        
<Setter Property="Height" Value="30"/>
        
<Setter Property="Background" Value="#FF1F3B53"/>
        
<Setter Property="Foreground" Value="#FF000000"/>
        
<Setter Property="Padding" Value="3"/>
        
<Setter Property="BorderThickness" Value="1"/>
        
<Setter Property="BorderBrush">
            
<Setter.Value>
                
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    
<GradientStop Color="#FFA3AEB9" Offset="0"/>
                    
<GradientStop Color="#FF8399A9" Offset="0.375"/>
                    
<GradientStop Color="#FF718597" Offset="0.375"/>
                    
<GradientStop Color="#FF617584" Offset="1"/>
                
</LinearGradientBrush>
            
</Setter.Value>
        
</Setter>
        
<Setter Property="Template">
            
<Setter.Value>
                
<ControlTemplate TargetType="src:SpeedButton">
                    
<Grid>
                        
<vsm:VisualStateManager.VisualStateGroups>
                            
<vsm:VisualStateGroup x:Name="CommonStates">
                                
<vsm:VisualState x:Name="Normal"/>
                                
<vsm:VisualState x:Name="MouseOver">
                                    
<Storyboard>
                                        
<DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundAnimation" Storyboard.TargetProperty="Opacity" To="1"/>
                                        
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#F2FFFFFF"/>
                                        
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#CCFFFFFF"/>
                                        
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#7FFFFFFF"/>
                                    
</Storyboard>
                                
</vsm:VisualState>
                                
<vsm:VisualState x:Name="Pressed">
                                    
<Storyboard>
                                        
<ColorAnimation Duration="0" Storyboard.TargetName="Background" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="#FF6DBDD1"/>
                                        
<DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundAnimation" Storyboard.TargetProperty="Opacity" To="1"/>
                                        
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)" To="#D8FFFFFF"/>
                                        
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#C6FFFFFF"/>
                                        
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#8CFFFFFF"/>
                                        
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#3FFFFFFF"/>
                                    
</Storyboard>
                                
</vsm:VisualState>
                            
</vsm:VisualStateGroup>
                        
</vsm:VisualStateManager.VisualStateGroups>
                        
<Border x:Name="Background" CornerRadius="3" Background="White" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
                            
<Grid Background="{TemplateBinding Background}"  Margin="1">
                                
<Border Opacity="0"  x:Name="BackgroundAnimation" Background="#FF448DCA" />
                                
<Rectangle x:Name="BackgroundGradient" >
                                    
<Rectangle.Fill>
                                        
<LinearGradientBrush StartPoint=".7,0" EndPoint=".7,1">
                                            
<GradientStop Color="#FFFFFFFF" Offset="0" />
                                            
<GradientStop Color="#F9FFFFFF" Offset="0.375" />
                                            
<GradientStop Color="#E5FFFFFF" Offset="0.625" />
                                            
<GradientStop Color="#C6FFFFFF" Offset="1" />
                                        
</LinearGradientBrush>
                                    
</Rectangle.Fill>
                                
</Rectangle>
                            
</Grid>
                        
</Border>
                        
<ContentPresenter
                              
x:Name="contentPresenter"
                              Content
="{TemplateBinding Content}"
                              ContentTemplate
="{TemplateBinding ContentTemplate}"
                              VerticalAlignment
="{TemplateBinding VerticalContentAlignment}"
                              HorizontalAlignment
="{TemplateBinding HorizontalContentAlignment}"
                              Margin
="{TemplateBinding Padding}"/>
                    
</Grid>
                
</ControlTemplate>
            
</Setter.Value>
        
</Setter>
    
</Style>
</ResourceDictionary>

 

这段代码其实是从Button的模板直接拷贝过来的,这里只是稍作修改,把用不上的东西去掉,如Focus状态,Button模板可以在Silverlight的帮助文档中获取。我懒得去重新设计外观了,将就用一下吧。

4、             打开SpeedButton.cs文件,给SpeedButton类添加构造函数如下:

 

1 public class SpeedButton : ContentControl
2     {
3         public SpeedButton()
4         {
5             DefaultStyleKey = typeof(SpeedButton);
6         }
7 }
8 

 

5、             修改MainPage.xaml文件,更改SpeedButton的声明如下:

 

<src:SpeedButton Content="HEX" />

 

6、运行程序,现在一个完整钮呈现在我们的面前,如下图所示。

 

当我们把鼠标指针移到按钮上方或按下按钮时无任何反应,这时需要对鼠标的事件进行处理,指定这些事件的可视状态。

 

第三步,给按钮添加鼠标响应事件:

虽然我们给按钮设定了模板,并在模板中指定了鼠标经过及按钮按下时的状态,但我们并没有在SpeedButton中指定何时呈现这些状态。下面处理SpeedButton的鼠标事件,并指定相应的可视状态。

打开SpeedButton.cs文件,并修改代码如下:

 

 1 public class SpeedButton : ContentControl
 2     {
 3         public SpeedButton()
 4         {
 5             DefaultStyleKey = typeof(SpeedButton);
 6             //指定鼠标事件方法
 7             MouseEnter += new MouseEventHandler(SpeedButton_MouseEnter);
 8             MouseLeave += new MouseEventHandler(SpeedButton_MouseLeave);
 9             MouseLeftButtonDown += new MouseButtonEventHandler(SpeedButton_MouseLeftButtonDown);
10         }
11         private Boolean isPress = false//指示按钮是否处于按下状态
12         public Boolean IsPress
13         {
14             get
15             {
16                 return isPress;
17             }
18             set
19             {
20                 if (IsPress == value) return;
21                 isPress = value;
22                 if (isPress)
23                 {   //将按钮显示为模板中的Pressed可视状态。
24                     VisualStateManager.GoToState(this"Pressed"true);
25                 }
26                 else
27                 {
28                     VisualStateManager.GoToState(this"Normal"true);
29                 }
30             }
31         }
32         void SpeedButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
33         {
34             IsPress = !IsPress;
35         }
36 
37         void SpeedButton_MouseLeave(object sender, MouseEventArgs e)
38         {
39             if (!isPress)
40             {
41                 VisualStateManager.GoToState(this"Normal"true);
42             }
43         }
44 
45         void SpeedButton_MouseEnter(object sender, MouseEventArgs e)
46         {
47             if (!isPress)
48             {
49                 VisualStateManager.GoToState(this"MouseOver"true);
50             }
51         }
52 }
53 

 

运行程序,用鼠标单击按钮看看有何不同。至此,一个简单的SpeedButton制作完毕。

 

第四步,制作ToolBar控件:

工具按钮是有了,接下来开始制作工具条。工具条其实就是一个容器,里面存放着几个工具按钮,当单击其中一个按钮时,另一个处于按下状态的按钮会自动弹上来,并且可以随时知道工具条当前按下的是哪个按钮。工具条的技术难题在于,当点击按钮改变工具条的选中项时,工具条如何知道按钮被点击,并触发一个事件来通知用户呢?按钮虽然拥有MouseLeftButtonDown事件,但如何把这个事件传递给工具条呢?

最初的想法是制作一个通用的工具条,但说实话,水平还不够,Silverlight中的很多机制还未了解透彻。而且通用控件需要考虑的东西太多,光代码可能就比位运算器多很多。这次还是制作一个位运算器专用的控件吧,好处是代码非常少,也容易理解。等未来水平达到了再考虑制作通用控件吧。

1、在BitLibrary】项目上单击鼠标右键,选择【添加】à【新建项】,添加一个Silverlight用户控件,并命名为“ToolBar.xaml”。更改ToolBar.xaml文件如下:

 

<UserControl x:Class="BitLibrary.ToolBar"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:src
="clr-namespace:BitLibrary;assembly=BitLibrary">
    
<StackPanel x:Name="LayoutRoot" Height="30" Width="120" Loaded="LayoutRoot_Loaded"
                Orientation
="Horizontal"
                VerticalAlignment
="Center"
                HorizontalAlignment
="Center">
        
<src:SpeedButton x:Name="btnBin" Content="BIN"/>
        
<src:SpeedButton x:Name="btnOct" Content="OCT"/>
        
<src:SpeedButton x:Name="btnBcd" Content="BCD"/>
        
<src:SpeedButton x:Name="btnHex" Content="HEX"/>
    
</StackPanel>
</UserControl>

 

ToolBar控件只是由一个StackPanel装载了四个SpeedButton控件,并且每个按钮都命了名,以方便在后台代码文件中调用。

接下来在Loaded="LayoutRoot_Loaded"上单击鼠标右键,在弹出菜单中选择【导航到事件处理程序】,这时自动打开ToolBar.xaml.cs文件,并自动生成了LayoutRoot_Loaded事件方法。

2、打开MainPage.xaml文件,并将<Grid>标签内的控件改为ToolBar:

<src:ToolBar/>

运行程序,这时带有四个SpeedButton的工具条呈现,单击每个按钮,可以发现当按下一个按钮时,其他按钮不会自动弹起。下面着手解决这个问题。

 

第五步,让工具条内的按钮互斥:

1、工具条内的四个按钮分别代表四种状态:

BIN:二进制

OCT:八进制

BCD:十进制

HEX:十六进制

为了方便后面写程序,这里声明一个枚举来表示这四种状态。

BitLibrary】项目上单击鼠标右键,选择【添加】à【新建项】,生成一个新类,命名为:“SystemState.cs”。在其中输入如下代码:

 

1 public enum SystemState
2 {
3         None,
4         Bin,
5         Oct,
6         Bcd,
7         Hex
8 }
9 

 

2、给按钮声明一个SystemState属性,用于保存这个按钮所属状态。打开SpeedButton.cs文件,并在SpeedButton添加一个State属性:

 

1 public SystemState State
2     {
3         get;
4         set;
5 }

3、打开ToolBar.xaml.cs文件,更改代码如下:

 

 1 public partial class ToolBar : UserControl
 2     {   //声明一个路由事件SelectionChange,用于在状态改变时通知用户
 3         public event RoutedEventHandler SelectionChange;
 4         public ToolBar()
 5         {
 6             InitializeComponent();
 7         }
 8 
 9         private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
10         {   //设置每个按钮所属状态
11             btnBin.State = SystemState.Bin;
12             btnOct.State = SystemState.Oct;
13             btnBcd.State = SystemState.Bcd;
14             btnHex.State = SystemState.Hex;
15             //将每个按钮的MouseLeftButtonDown事件连接至ToolBar的btn_MouseLeftButtonDown方法之中
16             btnBin.MouseLeftButtonDown += new MouseButtonEventHandler(btn_MouseLeftButtonDown);
17             btnOct.MouseLeftButtonDown += new MouseButtonEventHandler(btn_MouseLeftButtonDown);
18             btnBcd.MouseLeftButtonDown += new MouseButtonEventHandler(btn_MouseLeftButtonDown);
19             btnHex.MouseLeftButtonDown += new MouseButtonEventHandler(btn_MouseLeftButtonDown);
20         }
21         private SystemState toolState = SystemState.None;
22         public SystemState ToolState //表示工具条当前所属状态
23         {
24             get { return toolState; }
25             set
26             {
27                 if (toolState == value) return;
28                 toolState = value;
29                 //恢复每个按钮的正常状态
30                 btnBin.IsPress = false;
31                 btnOct.IsPress = false;
32                 btnBcd.IsPress = false;
33                 btnHex.IsPress = false;
34                 //根据当前状态使相应按钮处于按下状态
35                 switch (toolState)
36                 {
37                     case SystemState.Bin:
38                         btnBin.IsPress = true;
39                         break;
40                     case SystemState.Oct:
41                         btnOct.IsPress = true;
42                         break;
43                     case SystemState.Bcd:
44                         btnBcd.IsPress = true;
45                         break;
46                     case SystemState.Hex:
47                         btnHex.IsPress = true;
48                         break;
49                 }
50                 //触发SelectionChange事件
51                 if (SelectionChange != null
52                 {
53                     SelectionChange(thisnew RoutedEventArgs());
54                 }
55             }
56         }
57         //所有按钮单击时都会连接至这个方法
58         void btn_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
59         {   //通过设置ToolState属性以执行set访问器代码
60             this.ToolState = ((SpeedButton)sender).State;
61         }
62 }
63 

 

这里我们把所有按钮的单击事件连接至ToolBar类里的btn_MouseLeftButtonDown()法之中,这样按钮的单击事件就可以传递至ToolBar工具条了。呵呵,这一招是看ToolKit源码时学到的,这方法够巧妙。

现在可以运行程序,可以发现,当按下一个按钮时,之前被按下的按钮会自动弹起。

4、为了测试我们按下按钮时是否可以马上知道工具条当前状态,更改MainPage.xaml代码如下:

 

<UserControl x:Class="BitCalculator.MainPage"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d
=http://schemas.microsoft.com/expression/blend/2008 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:src
="clr-namespace:BitLibrary;assembly=BitLibrary"
    mc:Ignorable
="d" d:DesignWidth="120" d:DesignHeight="70">
    
<StackPanel x:Name="LayoutRoot">
        
<src:ToolBar x:Name="toolBar" Grid.Row="1" SelectionChange="toolBar_SelectionChange"/>
        
<TextBlock x:Name="txtMsg" Width="120" TextAlignment="Center" FontSize="13" Margin="10"/>
    
</StackPanel>
</UserControl>

 

打开MainPage.xaml.cs文件,并更改代码如下:

 

 1 public partial class MainPage : UserControl
 2 {
 3         public MainPage()
 4         {
 5             InitializeComponent();
 6         }
 7 
 8         private void toolBar_SelectionChange(object sender, RoutedEventArgs e)
 9         {
10             txtMsg.Text = "你按下了" + toolBar.ToolState.ToString() + "按钮";
11         }
12 }

 

现在就完成了如文章开头所演示的效果。待续。。。。。。

posted @ 2009-11-19 00:11  abatei  阅读(2114)  评论(2编辑  收藏  举报