【C#学习之路】WPF MVVM模式实现屏幕取色器


前言

对无外观控件的学习记录,自定义无外观控件,使用WPF纯MVVM模式实现屏幕取色器。
在这里插入图片描述


一、引入库

1.CommunityToolkit.Mvvm 包(详情)(又名 MVVM 工具包,以前称为 Microsoft.Toolkit.Mvvm)是一个现代、快速和模块化的 MVVM 库。

2.Microsoft.Xaml.Behaviors.Wpf详情),可以实现复杂命令绑定,而不是单纯的点击触发的Command。


二、创建无外观控件库

在这里插入图片描述
项目创建好之后会自动创建一个Themes文件夹,在此文件夹下包含一个Generic.xaml文件。
1.Generic.xaml资源文件可以创建该控件的默认样式模版。General.xmal名称不能随意修改,该资源文件必须在项目的根目录下的Therems目录中,作用是全局管理所有的控件默认模版样式。
2.可以创建多个样式资源文件,但最终必须合并到全局的General.xaml(项目目录下的Themes文件夹中)中。

XAML代码

前台样式资源文件代码

使用了三种绑定方式:
TemplateBinding:轻量级,只能单向通知
Binding 的相对绑定方式 RelativeSource,可以实现双向通知
后台C#代码绑定:需要为前台空间命名,PART_名称

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ColorLibrary">
    <Style TargetType="Slider" x:Key="SliderStyle">
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="Maximum" Value="255" />
        <Setter Property="Minimum" Value="0" />
        <Setter Property="TickPlacement" Value="BottomRight"/>
    </Style>

    <Style TargetType="{x:Type local:ColorPicker}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ColorPicker}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="auto"/>
                            <RowDefinition Height="auto"/>
                            <RowDefinition Height="auto"/>
                            <RowDefinition Height="auto"/>
                            <RowDefinition Height="auto"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition />
                            <ColumnDefinition Width="auto"/>
                        </Grid.ColumnDefinitions>

                        <!-- Three Binding Method -->

                        <!--{TemplateBinding Green} same as {Binding RelativeSource={RelativeSource Mode=TemplatedParent }, Path=Blue}-->

                        <Slider 
                            Grid.Row="0"
                            Grid.Column="0" 
                            x:Name="PART_RedSlider"
                            Background="Red"
                            Margin="{TemplateBinding Padding}"
                            Style="{StaticResource SliderStyle}"
                            />
                        <Slider
                            Grid.Row="1"
                            Grid.Column="0"
                            x:Name="PART_GreenSlider"
                            Margin="{TemplateBinding Padding}"
                            Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent }, Path=Green}"
                            Background="Green"
                            Style="{StaticResource SliderStyle}"
                            />
                        <Slider
                            Grid.Row="2"
                            Grid.Column="0"
                            x:Name="PART_BlueSlider"
                            Margin="{TemplateBinding Padding}"
                            Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent }, Path=Blue}"
                            Background="Blue"
                            Style="{StaticResource SliderStyle}"
                            />

                        <Slider
                            Grid.Row="3"
                            Grid.Column="0"
                            x:Name="PART_AlphaSlider"
                            Margin="{TemplateBinding Padding}"
                            Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent }, Path=Alpha}"
                            Style="{StaticResource SliderStyle}"
                            />
                        <Border  Grid.RowSpan="4" Grid.Column="1"  Width="200"  Margin="{TemplateBinding Padding}" CornerRadius="5" >
                            <Border.Background>
                                <SolidColorBrush  x:Name="PART_Brush"/>
                            </Border.Background>
                            <StackPanel>
                                <TextBlock Text="Sample Text" Foreground="Black" FontSize="30"/>
                                <TextBlock Text="Sample Text" Foreground="White" FontSize="30"/>
                            </StackPanel>
                        </Border>
                        <StackPanel Grid.Row="4" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="{Binding ElementName=PART_RedSlider, Path=Margin}">
                            <StackPanel Orientation="Horizontal" Margin="0 0 10 0">
                                <TextBlock Text="Red" Grid.Column="0"  VerticalAlignment="Center" Margin="0 0 3 0"/>
                                <TextBox  Width="30"  Text="{Binding ElementName=PART_RedSlider, Path=Value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="Center" VerticalAlignment="Center" Grid.Column="1"/>
                            </StackPanel>
                            <StackPanel Orientation="Horizontal" Margin="0 0 10 0">
                                <TextBlock Text="Green" Grid.Column="0" VerticalAlignment="Center" Margin="0 0 3 0"/>
                                <TextBox Width="30" Text="{Binding ElementName=PART_GreenSlider, Path=Value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="Center" VerticalAlignment="Center" Grid.Column="1"/>
                            </StackPanel>

                            <StackPanel Orientation="Horizontal" Margin="0 0 10 0">
                                <TextBlock Text="Blue" Grid.Column="0" VerticalAlignment="Center" Margin="0 0 3 0"/>
                                <TextBox Width="30" Text="{Binding ElementName=PART_BlueSlider, Path=Value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="Center" VerticalAlignment="Center" Grid.Column="1"/>
                            </StackPanel>

                            <StackPanel Orientation="Horizontal" Margin="0 0 10 0">
                                <TextBlock Text="Alpha" Grid.Column="0" VerticalAlignment="Center" Margin="0 0 3 0"/>
                                <TextBox Width="30" Text="{Binding ElementName=PART_AlphaSlider, Path=Value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="Center" VerticalAlignment="Center" Grid.Column="1"/>
                            </StackPanel>

                            <StackPanel Orientation="Horizontal" Margin="0 0 10 0">
                                <TextBlock Text="ARGB" Grid.Column="0" VerticalAlignment="Center" Margin="0 0 3 0"/>
                                <TextBox  Width="100"  Text="{Binding ElementName=PART_Brush, Path=Color,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="Center" VerticalAlignment="Center" Grid.Column="1"/>
                            </StackPanel>
                        </StackPanel>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
后台逻辑代码
public class ColorPicker : Control
 {

     #region 01依赖项属性
     //1.声明依赖项属性
     public static readonly DependencyProperty ColorProperty;
     public static readonly DependencyProperty RedProperty;
     public static readonly DependencyProperty GreenProperty;
     public static readonly DependencyProperty BlueProperty;
     public static readonly DependencyProperty AlphaProperty;
     //2.包装依赖项属性,依赖项属性也是一种属性,所以要包装一下,暴露出一般属性的特性方便使用
     public Color MyColor
     {
         get { return (Color)GetValue(ColorProperty); }
         set { SetValue(ColorProperty, value); }
     }

     public byte Red
     {
         get { return (byte)GetValue(RedProperty); }
         set { SetValue(RedProperty, value); }
     }

     public byte Green
     {
         get { return (byte)GetValue(GreenProperty); }
         set { SetValue(GreenProperty, value); }
     }

     public byte Blue
     {
         get { return (byte)GetValue(BlueProperty); }
         set { SetValue(BlueProperty, value); }
     }

     public byte Alpha
     {
         get { return (byte)GetValue(AlphaProperty); }
         set { SetValue(AlphaProperty, value); }
     }

     #endregion

     #region 02路由事件
     //1.定义路由事件
     public static readonly RoutedEvent ColorChangedEvent;

     //2.包装路由事件
     public event RoutedPropertyChangedEventHandler<Color> ColorChanged
     {
         add { AddHandler(ColorChangedEvent, value); }
         remove { RemoveHandler(ColorChangedEvent, value); }
     }
     #endregion

     #region 03注册依赖项属性与路由事件
     //3.注册依赖项属性与路由事件
     //注意:必须再使用依赖项属性之前注册依赖项属性,所以依赖项属性总是再所在类静态构造函数或则静态字段中注册
     static ColorPicker()
     {
         //从样式中加载模板,固定写法
         DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorPicker), new FrameworkPropertyMetadata(typeof(ColorPicker)));

         //注册依赖项属性
         ColorProperty = DependencyProperty.Register(
             name: "MyColor",
             propertyType: typeof(Color),
             ownerType: typeof(ColorPicker),
             typeMetadata: new PropertyMetadata(propertyChangedCallback: OnColorChanged) { DefaultValue = Colors.Black },//属性改变时触发的回调
             validateValueCallback: ValidateValue//验证
             );

         RedProperty = DependencyProperty.Register(
             name: "Red",
             propertyType: typeof(byte),
             ownerType: typeof(ColorPicker),
             typeMetadata: new PropertyMetadata(propertyChangedCallback: OnRGBChanged),//属性改变时触发的回调
             validateValueCallback: ValidateValue//验证
             );

         GreenProperty = DependencyProperty.Register(
             name: "Green",
             propertyType: typeof(byte),
             ownerType: typeof(ColorPicker),
             typeMetadata: new PropertyMetadata(propertyChangedCallback: OnRGBChanged),
             validateValueCallback: ValidateValue
             );

         BlueProperty = DependencyProperty.Register(
             name: "Blue",
             propertyType: typeof(byte),
             ownerType: typeof(ColorPicker),
             typeMetadata: new PropertyMetadata(propertyChangedCallback: OnRGBChanged),
             validateValueCallback: ValidateValue
             );

         AlphaProperty = DependencyProperty.Register(
             name: "Alpha",
             propertyType: typeof(byte),
             ownerType: typeof(ColorPicker),
             typeMetadata: new PropertyMetadata(propertyChangedCallback: OnRGBChanged),
             validateValueCallback: ValidateValue
             );

         //注册路由事件
         ColorChangedEvent = EventManager.RegisterRoutedEvent(
             name: "ColorChanged",//事件名
             routingStrategy: RoutingStrategy.Bubble,//路由类型,冒泡
             handlerType: typeof(RoutedPropertyChangedEventHandler<Color>),//事件类型
             ownerType: typeof(ColorPicker)//属于哪个对象
             );
     }
     #endregion

     #region 04回调函数
     public static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
     {
         ColorPicker picker = d as ColorPicker;
         //MyColor属性改变后会触发OnColorChanged()回调函数,获得新值
         Color newcolor = (Color)e.NewValue;
         Color oldcolor = (Color)e.OldValue;


         //解析Color解析到RGB
         picker.Red = newcolor.R;
         picker.Green = newcolor.G;
         picker.Blue = newcolor.B;
         picker.Alpha = newcolor.A;

         //颜色改变后调用事件
         RoutedPropertyChangedEventArgs<Color> args =
             new RoutedPropertyChangedEventArgs<Color>(oldcolor, newcolor, ColorChangedEvent);

         picker?.RaiseEvent(args);
     }

     public static void OnRGBChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
     {
         ColorPicker picker = d as ColorPicker;
         Color color = picker.MyColor;


         if (e.Property == RedProperty)
         {
             color.R = (byte)e.NewValue;
         }

         if (e.Property == GreenProperty)
         {
             color.G = (byte)e.NewValue;
         }

         if (e.Property == BlueProperty)
         {
             color.B = (byte)e.NewValue;
         }

         if (e.Property == AlphaProperty)
         {
             color.A = (byte)e.NewValue;
         }

         picker.MyColor = color;
     }
     #endregion

     #region 05验证
     public static bool ValidateValue(object ob)
     {
         return true;
     }
     #endregion

     /// <summary>
     /// 当  DefaultStyleKeyProperty.OverrideMetadata初始化完成Style时回调
     /// </summary>
     public override void OnApplyTemplate()
     {
         base.OnApplyTemplate();

         //在后台代码手动建立数据绑定,必须给元素命名,方式为:PART_开头,后跟元素名称
         RangeBase redSlider = GetTemplateChild("PART_RedSlider") as RangeBase;

         Binding binding = new Binding("Red") //Path = Red
         {
             Source = this, //源
             Mode = BindingMode.TwoWay
         };
         redSlider?.SetBinding(RangeBase.ValueProperty, binding);


         //建立Brush的绑定,由于Brush没有SetBinding对象,只能this当作目标,Brush当作源,OneWayToSource,目标改变时改变源
         Brush brush = GetTemplateChild("PART_Brush") as Brush;

         Binding binding1 = new Binding("Color")
         {
             Source = brush,
             Mode = BindingMode.TwoWay
         };
         this.SetBinding(ColorProperty, binding1);
     }
 }


三、核心功能实现

获取鼠标所在位置颜色

鼠标按下时打开计时器,每隔一段时间获取光标所在的位置。

 		private DispatcherTimer timer = new DispatcherTimer();
        public MainWindowViewMode()
        {
            timer.Interval = TimeSpan.FromSeconds(0.01);
            timer.Tick += timer_Tick;

            MouseDownCommand = new RelayCommand<Button>(MouseDown);
            MouseUpCommand = new RelayCommand<Button>(MouseUp);
        }
        [DllImport("gdi32")]
        private static extern int GetPixel(int hdc, int nXPos, int nYPos);

        [DllImport("user32")]
        private static extern int GetWindowDC(int hwnd);

        [DllImport("user32")]
        private static extern int ReleaseDC(int hWnd, int hDC);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern bool GetCursorPos(out POINT pt);
        private void timer_Tick(object sender, EventArgs e)
        {
            GetCursorPos(out var pt);
            Point point = new Point(pt.X, pt.Y);
            MyColor = GetPixelColor(point);

            Red = MyColor.R;
            Green = MyColor.G;
            Blue = MyColor.B;
            Alpha = MyColor.A;
        }

        private Color GetPixelColor(Point point)
        {
            int windowDC = GetWindowDC(0);
            int pixel = GetPixel(windowDC, (int)point.X, (int)point.Y);
            ReleaseDC(0, windowDC);
            byte b = (byte)((ulong)(pixel >> 16) & 0xFFuL);
            byte g = (byte)((ulong)(pixel >> 8) & 0xFFuL);
            byte r = (byte)((ulong)pixel & 0xFFuL);
            return Color.FromRgb(r, g, b);
        }

引用无外观控件

xmlns:uc="clr-namespace:ColorLibrary;assembly=ColorLibrary"

<uc:ColorPicker x:Name="picker" Padding="10" Red="{Binding Red,Mode=TwoWay}"  Green="{Binding Green,Mode=TwoWay}" Blue="{Binding Blue,Mode=TwoWay}" Alpha="{Binding Alpha,Mode=TwoWay}"/>

添加鼠标按下的命令

 xmlns:I="http://schemas.microsoft.com/xaml/behaviors"
 
  <I:Interaction.Triggers>
      <I:EventTrigger EventName="MouseDown">
          <I:InvokeCommandAction Command="{Binding MouseDownCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}}" />
      </I:EventTrigger>
      <I:EventTrigger EventName="MouseUp">
          <I:InvokeCommandAction Command="{Binding MouseUpCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}}" />
      </I:EventTrigger>
  </I:Interaction.Triggers>

       public MainWindowViewMode()
       {
           MouseDownCommand = new RelayCommand<Button>(MouseDown);
           MouseUpCommand = new RelayCommand<Button>(MouseUp);
       }
        public RelayCommand<Button> MouseDownCommand { get; set; }
        public RelayCommand<Button> MouseUpCommand { get; set; }
        
        public void MouseDown(Button button)
        {
            button.CaptureMouse();
            button.Content = "松开完成";
            timer.Start();
        }
        public void MouseUp(Button button)
        {
            button.ReleaseMouseCapture();
            button.Content = "按住取色";
            timer.Stop();
        }

总结

无外观控件灵活性高,控件的样式不是固定的,可以在使用时重写控件模板。完整代码

  • 22
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值