zoukankan      html  css  js  c++  java
  • [WPF] UserControl vs CustomControl

    介绍

    WPF中有两种控件:UserControl和CustomControl,但是这两者有什么区别呢?这篇博客中将介绍两者之间的区别,这样可以在项目中合理的使用它们。

    UserControl

    • 将多个WPF控件(例如:TextBox,TextBlock,Button)进行组合成一个可复用的控件组;
    • 由XAML和Code Behind代码组成;
    • 不支持样式/模板重写;
    • 继承自UserControl;

    下面创建的一个RGBControl由3个TextBlock,3个TextBox,1个Rectangle组成。我们可以在WPF的任意窗体/Page上面复用该UserControl。

    XAML Code:

    <Grid Background="LightGray">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="2*" />
        </Grid.ColumnDefinitions>
    
        <TextBlock Text="Red" />
        <TextBlock Text="Green" Grid.Row="1" />
        <TextBlock Text="Blue" Grid.Row="2" />
    
        <TextBox Text="{Binding Red, UpdateSourceTrigger=PropertyChanged}" 
                 VerticalContentAlignment="Center" Grid.Column="1" Height="25" Width="80" Margin="0,5" />
        <TextBox Text="{Binding Green, UpdateSourceTrigger=PropertyChanged}" 
                 VerticalContentAlignment="Center" Grid.Row="1" Grid.Column="1" Height="25" Width="80" Margin="0,5" />
        <TextBox Text="{Binding Blue, UpdateSourceTrigger=PropertyChanged}" 
                 VerticalContentAlignment="Center" Grid.Row="2" Grid.Column="1" Height="25" Width="80" Margin="0,5" />
    
        <Rectangle Fill="{Binding Color, Converter={StaticResource ColorToSolidBrushConverter}}" 
                   Grid.Column="2" Grid.RowSpan="3" Margin="10, 5" Width="100" Height="100"/>
    </Grid>

    C# Code

    public partial class RGBControl : UserControl
    {
        public RGBControl()
        {
            InitializeComponent();
    
            this.DataContext = new RGBViewModel();
        }
    }
    
    public class RGBViewModel : ObservableObject
    {
        private byte _red = 0;
        public byte Red
        {
            get
            {
                return _red;
            }
            set
            {
                if(_red != value)
                {
                    _red = value;
                    RaisePropertyChanged("Red");
                    RaiseColorChanged();
                }
            }
        }
    
        private byte _green = 0;
        public byte Green
        {
            get
            {
                return _green;
            }
            set
            {
                if(_green != value)
                {
                    _green = value;
                    RaisePropertyChanged("Green");
                    RaiseColorChanged();
                }
            }
        }
    
        private byte _blue = 0;
        public byte Blue
        {
            get
            {
                return _blue;
            }
            set
            {
                if(_blue != value)
                {
                    _blue = value;
                    RaisePropertyChanged("Blue");
                    RaiseColorChanged();
                }
            }
        }
    
        private Color _color;
        public Color Color
        {
            get
            {
                return _color;
            }
            set
            {
                RaiseColorChanged();
            }
        }
    
        private void RaiseColorChanged()
        {
            _color = Color.FromRgb(Red, Green, Blue);
    
            RaisePropertyChanged("Color");
        }
    }
    
    public class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    
    public class ColorToSolidBrushConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Color color = (Color)value;
    
            return new SolidColorBrush(color);
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }
    View Code

    使用RGBControl:

    <Grid>
        <local:RGBControl Width="320" Height="120"/>
    </Grid>

    CustomControl

    • 自定义控件,扩展自一个已经存在的控件,并添加新的功能/特性;
    • 由C#/VB.NET Code和样式文件组成(Themes/Generic.xaml);
    • 支持样式/模板重写;
    • 如果项目中自定义控件较多,建议创建一个WPF自定义控件库(WPF Control Library)

    怎样创建一个WPF CustomControl呢?

    选择合适的控件基类,或者说选择合适的控件进行功能扩展

    UIElement 最轻量级的基类,支持Layout, Input, Focus, Event

    FrameworkElement 继承自UIElement,支持styling,tooltips,context menus,data binding,resouce look up

    Control 最基础的控件,支持template, 并增加了一些额外属性,例如Foreground, Background, FontSize等

    ContentControl 在Control的基础上增加了Content属性,常见的控件有,布局控件,Button等

    HeaderedContentControl 在ContentControl基础增加了一个Header属性,常见的控件有:Expander,TabControl,GroupBox等

    ItemsControl 一个具有Items集合的控件,用来展示数据,但是不包含 Selection 特性

    Selector 是一个ItemsControl,增加了Indexed,Selected特性,典型的控件有: ListBox, ComboBox, ListView, TabControl等

    RangeBase 典型的控件有Sliders, ProgressBars. 增加了Value,Minimum和Maximum属性

    WPF的控件行为和表现是分离的。行为在Code中定义,Template在XAML中定义。

    重写Default Style

    static NumericTextBox()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericTextBox), 
            new FrameworkPropertyMetadata(typeof(NumericTextBox)));
    }

    重写默认样式文件

    <Style TargetType="{x:Type local:NumericTextBox}">
    
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:NumericTextBox}">
                    ...
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    以一个Numeric up/down控件为例:控件如下:

    很直观的可以看到,Numeric up/down TextBox可以通过扩展WPF的TextBox控件实现,在WPF TextBox的基础上添加两个Button,然后重写这个自定义控件样式。

    C# Code:

    [TemplatePart(Name = UpButtonKey, Type = typeof(Button))]
    [TemplatePart(Name = DownButtonKey, Type = typeof(Button))]
    public class NumericTextBox : TextBox
    {
        private const string UpButtonKey = "PART_UpButton";
        private const string DownButtonKey = "PART_DownButton";
    
        private Button _btnUp = null;
        private Button _btnDown = null;
    
        static NumericTextBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericTextBox), 
                new FrameworkPropertyMetadata(typeof(NumericTextBox)));
        }
    
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
    
            _btnUp = Template.FindName(UpButtonKey, this) as Button;
            _btnDown = Template.FindName(DownButtonKey, this) as Button;
    
            _btnUp.Click += delegate { Operate("+"); };
            _btnDown.Click += delegate { Operate("-"); };
        }
    
        private void Operate(string operation)
        {
            int input = 0;
    
            if(int.TryParse(this.Text, out input))
            {
                if (operation == "+")
                {
                    this.Text = (input + 1).ToString();
                }
                else
                {
                    this.Text = (input - 1).ToString();
                }
            }
        }
    }

    Style Code:

    <Style TargetType="{x:Type local:NumericTextBox}">
        <Setter Property="SnapsToDevicePixels" Value="True" />
        <Setter Property="FontSize" Value="12" />
        <Setter Property="Height" Value="40" />
        
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:NumericTextBox}">
                    <Border x:Name="OuterBorder" BorderBrush="LightGray" BorderThickness="1">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="30" />
                            </Grid.ColumnDefinitions>
                            <Border Grid.ColumnSpan="2" Grid.RowSpan="2" Background="White">
                                <ScrollViewer x:Name="PART_ContentHost" Margin="5,0" VerticalAlignment="Center" FontSize="12" />
                            </Border>
                            <Button x:Name="PART_UpButton" Grid.Column="1" Content="+" VerticalContentAlignment="Center" />
                            <Button x:Name="PART_DownButton" Grid.Row="1" Grid.Column="1" Content="-" VerticalContentAlignment="Center" />
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    使用:

    <StackPanel>
        <custom:NumericTextBox Width="200" Text="1" />
    </StackPanel>

    感谢您的阅读~

    参考文章:

    https://wpftutorial.net/CustomVsUserControl.html

    https://wpftutorial.net/HowToCreateACustomControl.html

  • 相关阅读:
    二叉排序树的最低公共祖先
    [jobdu]树中两个结点的最低公共祖先
    [jobdu]用两个栈实现队列
    [leetcode]Balanced Binary Tree
    [jobdu]从尾到头打印链表
    [leetcode]Flatten Binary Tree to Linked List
    [leetcode]Unique Binary Search Trees
    hdu 4059
    hdu 3972 1 M possible
    CF 317D Game with Powers
  • 原文地址:https://www.cnblogs.com/yang-fei/p/7480136.html
Copyright © 2011-2022 走看看