wpf 寫個簡單的控件吧


今天看到了老趙的一片博客:編寫一個“綁定友好”的WPF控件

文章里面遇到的問題,蠻有意思,后面的評論,非常精彩,沒看過的,推薦看一下。

由於我也做過類似的需求,所以,貼出我當時的做法和現在的想法,僅僅是筆記,沒有其他意思。

當時的需求類似這樣子,實際做的效果當然比這個漂亮的多。

看到這個UI的第一反應就是,封裝一個控件,把slider包進去,很簡單的吧。

當時的做法,在CS代碼里面封裝幾個DP,綁定就完事兒了呀。

<UserControl x:Class="WpfControlTest.UCSlider"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="250" Height="45"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  x:Name="main"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  >
  <Grid>
    <TextBlock  Text="{Binding ElementName=main,Path=Text}" Margin="0,0,0,0" Foreground="Black" VerticalAlignment="Top"  HorizontalAlignment="Left"/>
    <TextBlock  Text="{Binding  ElementName=sl,Path=Value,StringFormat=0}" Foreground="Black" HorizontalAlignment="Right"/>
    <Slider   Name="sl"   Width="{Binding Path=Width,ElementName=main}" VerticalAlignment="Bottom" Cursor="Hand"  
                          Maximum="{Binding ElementName=main,Path=Maximum}" 
                          Minimum="{Binding ElementName=main,Path=Minimum}" 
              Value="{Binding ElementName=main,Path=SliderValue}" />
  </Grid>
</UserControl>

跟老趙那個類似,實際的使用場景也就是這么回事,cs代碼當時根本就沒想到用MVVM,基本沒有邏輯,沒必要分離。
只是需要綁定,所以DP少不了的。比較簡單,要細看的自己展開。

CS code
 1  /// <summary>
 2     /// when value changed,use eventhandle to notify
 3     /// </summary>
 4     /// <param name="value"></param>
 5     public delegate void CustomSliderEventHandle(double value);
 6 
 7     public partial class UCSlider : UserControl
 8     {
 9         public UCSlider()
10         {
11             InitializeComponent();
12             sl.ValueChanged += new RoutedPropertyChangedEventHandler<double>(sl_ValueChanged);
13         }
14 
15         #region event
16         public event CustomSliderEventHandle ValueChange;
17 
18         void sl_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
19         {
20             if (ValueChange != null)
21                 ValueChange(sl.Value);
22         }
23         #endregion
24 
25         #region Text
26         public string Text
27         {
28             get { return (string)GetValue(TextProperty); }
29             set { SetValue(TextProperty, value); }
30         }
31 
32         public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(UCSlider));
33         #endregion
34 
35         #region Maximum
36         public double Maximum
37         {
38             get { return (double)GetValue(MaximumProperty); }
39             set { SetValue(MaximumProperty, value); }
40         }
41 
42         public static DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(UCSlider));
43         #endregion
44 
45         #region Minimum
46         public double Minimum
47         {
48             get { return (double)GetValue(MinimumProperty); }
49             set { SetValue(MinimumProperty, value); }
50         }
51 
52         public static DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(double), typeof(UCSlider));
53         #endregion
54 
55         #region SliderValue
56         public double SliderValue
57         {
58             get { return (double)GetValue(SliderValueProperty); }
59             set { SetValue(SliderValueProperty, value); }
60         }
61 
62         public static DependencyProperty SliderValueProperty = DependencyProperty.Register("SliderValue", typeof(double), typeof(UCSlider));
63         #endregion
64     }

沒有用MVVM,所以比老趙那個簡單了不少。
到了調用的地方,貌似是搞個ViewModel的時候,其實只是用到了NotifyPropertyChanged,因為這個玩起來爽啊。

UI:

 <StackPanel HorizontalAlignment="Left" Margin="10,10,0,0" >
      <UC:UCSlider Text="Brightness" Maximum="100" Minimum="0"  SliderValue="{Binding Path=Brightness,Mode=TwoWay}"  >
      </UC:UCSlider>
      <UC:UCSlider Text="Contrast" Maximum="100" Minimum="0"  SliderValue="{Binding Path=Contrast,Mode=TwoWay}"/>
      <UC:UCSlider Text="Hue" Maximum="2" Minimum="-2"  SliderValue="{Binding Path=Hue,Mode=TwoWay}"/>
    </StackPanel>

CS:

CS Code
 1  /// <summary>
 2     /// Interaction logic for MainWindow.xaml
 3     /// </summary>
 4     public partial class MainWindow : Window
 5     {
 6         TurningViewModel _turningViewModel = new TurningViewModel() { Hue = 1, Brightness = 20, Contrast = 2 };
 7         public MainWindow()
 8         {
 9             InitializeComponent();
10             this.DataContext = _turningViewModel;
11             _turningViewModel.PropertyChanged += new PropertyChangedEventHandler(_turningViewModel_PropertyChanged);
12         }
13 
14         void _turningViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
15         {
16             Debug.WriteLine(string.Format("PropertyName:{0}", e.PropertyName));
17         }
18 
19         private void get_Click(object sender, RoutedEventArgs e)
20         {
21             Debug.WriteLine(string.Format("Hue:{0}", _turningViewModel.Hue));
22             Debug.WriteLine(string.Format("Brightness:{0}", _turningViewModel.Brightness));
23             Debug.WriteLine(string.Format("Contrast:{0}", _turningViewModel.Contrast));
24         }
25     }
26 
27     public class TurningViewModel : INotifyPropertyChanged
28     {
29         #region Hue
30         private double _hue;
31         public double Hue
32         {
33             get { return _hue; }
34             set
35             {
36                 if (_hue != value)
37                 {
38                     _hue = value;
39                     NotifyPropertyChanged("Hue");
40                 }
41             }
42         }
43         #endregion
44 
45         #region Brightness
46         private double _brightness;
47         public double Brightness
48         {
49             get { return _brightness; }
50             set
51             {
52                 if (_brightness != value)
53                 {
54                     _brightness = value;
55                     NotifyPropertyChanged("Brightness");
56                 }
57             }
58         }
59         #endregion
60 
61         #region Contrast
62         private double _contrast;
63         public double Contrast
64         {
65             get { return _contrast; }
66             set
67             {
68                 if (_contrast != value)
69                 {
70                     _contrast = value;
71                     NotifyPropertyChanged("Contrast");
72                 }
73             }
74         }
75         #endregion
76 
77         void NotifyPropertyChanged(string propertyName)
78         {
79             if (PropertyChanged != null)
80             {
81                 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
82             }
83         }
84 
85         public event PropertyChangedEventHandler PropertyChanged;
86     }

基本上就結束了呀。
如果要把這個控件做成自定義控件,繼承自Control的,

XAML:

View Code
 1  <Style TargetType="{x:Type local:CCSlider}">
 2     <Setter Property="Template">
 3       <Setter.Value>
 4         <ControlTemplate TargetType="{x:Type local:CCSlider}">
 5           <Border Background="{TemplateBinding Background}"
 6                             BorderBrush="{TemplateBinding BorderBrush}"
 7                             BorderThickness="{TemplateBinding BorderThickness}">
 8             <Grid Width="200" Height="45">
 9               <TextBlock  Text="{TemplateBinding Text}" Margin="0,0,0,0" Foreground="Black" VerticalAlignment="Top"  HorizontalAlignment="Left"/>
10               <TextBlock  Text="{Binding  ElementName=sl,Path=Value,StringFormat=0}" Foreground="Black" HorizontalAlignment="Right"/>
11               <Slider   Name="sl"   Width="{Binding Path=Width,ElementName=main}" VerticalAlignment="Bottom" Cursor="Hand"  
12                           Maximum="{TemplateBinding Maximum}" 
13                           Minimum="{TemplateBinding Minimum}" 
14                           Value="{Binding Path=SliderValue,Mode=TwoWay,RelativeSource={RelativeSource Mode=TemplatedParent}}" />
15             </Grid>
16           </Border>
17         </ControlTemplate>
18       </Setter.Value>
19     </Setter>
20   </Style>

CS 代碼和 上面那個一樣的。

View Code
 1     public class CCSlider : Control
 2     {
 3         static CCSlider()
 4         {
 5             DefaultStyleKeyProperty.OverrideMetadata(typeof(CCSlider), new FrameworkPropertyMetadata(typeof(CCSlider)));
 6         }
 7 
 8 
 9         #region Text
10         public string Text
11         {
12             get { return (string)GetValue(TextProperty); }
13             set { SetValue(TextProperty, value); }
14         }
15 
16         public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(CCSlider));
17         #endregion
18 
19         #region Maximum
20         public double Maximum
21         {
22             get { return (double)GetValue(MaximumProperty); }
23             set { SetValue(MaximumProperty, value); }
24         }
25 
26         public static DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(CCSlider));
27         #endregion
28 
29         #region Minimum
30         public double Minimum
31         {
32             get { return (double)GetValue(MinimumProperty); }
33             set { SetValue(MinimumProperty, value); }
34         }
35 
36         public static DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(double), typeof(CCSlider));
37         #endregion
38 
39         #region SliderValue
40         public double SliderValue
41         {
42             get { return (double)GetValue(SliderValueProperty); }
43             set { SetValue(SliderValueProperty, value); }
44         }
45 
46         public static DependencyProperty SliderValueProperty = DependencyProperty.Register("SliderValue", typeof(double), typeof(CCSlider));
47         #endregion
48     }

 

 

順便嘮叨:

1. 控件自定義有兩種:

一種是和邏輯相關的,里面可以搞搞ViewModel,分離一下。如果分離,DataContext必須指明,要不然和老趙的那個錯誤類似了。如果邏輯不復雜,就沒那個必要了,直接balala一搞就完事兒了。
如果業務粒度分的細,邏輯比較單純的,還是不要費那個事兒了,畢竟ViewModel的代碼量大啊,我承認自己懶。
這個ASP.net的模板頁面套子頁面一個道理。
這一種用UserControl搞搞,方便,感覺就是為了分割邏輯用的。

另外一種是和邏輯無關的,例如三態按鈕,要大量復用,或者要做類似RadioButton的效果,只有一個被選中。
那就繼承自Control吧。

這個區分方法也不是嚴格定義的,看實際需求吧。

2.再來說MVVM,關於這個東東的爭論貌似也不少,快趕上 JAVA和C#的爭論了。
不少的WPF開發者,潛意識里面,隨便寫個東東都MVVM,不管大大小小都搞個ViewModel。
有時候.xaml.cs里面幾乎啥都沒放,ViewModel的代碼倒是急劇膨脹,何苦啊。
MVVM的理論解釋,還是有微軟的說法,等等,都在MSDN里面,不再貼出來了。
只說說自己的體會:

剛開始搞WPF的時候,從ASP.net轉過來的,WinForm也玩過,習慣了代碼搞定一切。
於是乎,套用,幾乎一切都是代碼搞定,因為這個相對熟悉,而且調試方便啊。
后來項目湊合着搞完了,開始靜下心來看看WPF,看看MVVM,看看綁定啊等等東西。|
也做了Demo,發現如果熟悉XAML,挺好,一句可以代替CS代碼好多句,清爽,簡練,不熟悉,苦逼的事情很多。
MVVM,第一次用是在  數據的增刪改,新增家具,要填寫一堆的屬性,家具列表,修改家具...
按照之前的做法,寫個類,一個一個屬性的綁定,保存的時候,一個一個的獲取...,你懂的,十幾個屬性,會死人的。
用MVVM,只要在XAML里面綁定數據,UI邏輯需要訪問控件的,在.xaml.cs中寫一下,數據加工邏輯不會涉及控件的都在ViewModel中,獲取數據的時候直接取ViewModel。
這是我發現的最最適用的場合。


我想到最初做Winform的時候,有個叫DataGrid(名字不太確定)的控件,直接可以編輯表格內容,排序,增刪改都集成了。
當時是醫葯銷售行業,老大們把業務邏輯封裝在存儲過程中,UI就直接拿DataGrid,拖過來,給個數據源,保存的時候取一下數據,完事兒了。
並且賣錢了,還不少,這就是MS最牛叉的地方,直接用,直接賣錢。

數據增刪改,是直接產生效益的地方,一般的銷售類軟件都需要這個功能,微軟貌似就在這里下功夫,Asp.net 和Winfor都是,WPF的MVVM也是。

再回頭說說做的第一個項目,后來准備重構,弄個MVVM上去,發現不爽。
這個項目類似塗鴉牆,文字、圖片、Flash等等可以隨意的放在畫布上,而且可以移動、旋轉、縮放。
原因是數據加工邏輯基本沒有,就是保存,提取。邏輯集中在UI上,最要命的是移動、旋轉、縮放,而且還組合操作。
如果我用MVVM,倒也可以,但是還是用CS代碼控制比較爽。

說說現在的看法:
舉個例子,ListView,數據加載\加工的邏輯在ViewModel,雙擊列頭排序和雙擊條目跳轉這種邏輯在.xaml.cs中。
再舉個例子,帶有復選框文件樹,用TreeView實現的,勾選子結點,父節點被勾選還是咋的,純粹是數據層面的事情,那就在ViewModel中實現。
某個節點被選中了,要獲取當前的選中項數據,在Xaml.cs中顯然方便一點,在ViewModel中檢測PropertyChange當然也可以。


數據相關的在ViewModel中,需要訪問控件的在.xaml.cs中,當然了,有時候也可以做一下均衡。
只有后者的,不實現MVVM。
再扯遠點,就是數據驅動開發了。

其實就一句話:根據實際業務情況決定和重構。

 Demo

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM