WPF开发技巧之花式控件功能扩展详解

作者:小伟06 时间:2022-07-13 05:56:52 

目录
  • No1. 自定义控件模板

  • No2. 重写控件

  • No3. 附加属性来试试

  • 总结

文章默认你已经入门WPF了

WPF日常开发,经常遇到默认的控件功能不满足需求,怎么办?

No1. 自定义控件模板

平时开发中,经常遇到比较”俗“的需求,嫌弃控件默认的样子。怎么办?哈哈,那就整个容呗..... 😜!

还记得心灵深处的Button吗?是不是第一印象就是规规矩矩的长方形,好了,这次我们俗一下,把它变成圆形!

上代码:


<Button Content="Test1" Width="80" Height="80" FocusVisualStyle="{x:Null}" Background="LightSeaGreen" BorderBrush="DarkBlue">
               <Button.Template>
                   <ControlTemplate TargetType="ButtonBase">
                       <Grid>
                           <Ellipse x:Name="ellipseBorder" StrokeThickness="1" Stroke="{TemplateBinding BorderBrush}" Fill="{TemplateBinding Background}"/>
                           <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{TemplateBinding Content}"/>
                       </Grid>
                       <ControlTemplate.Triggers>
                           <Trigger Property="IsMouseOver" Value="True">
                               <Setter Property="Stroke" Value="Orange" TargetName="ellipseBorder"/>
                           </Trigger>
                           <Trigger Property="IsPressed" Value="True">
                               <Setter Property="Stroke" Value="OrangeRed" TargetName="ellipseBorder"/>
                           </Trigger>
                       </ControlTemplate.Triggers>
                   </ControlTemplate>
               </Button.Template>
           </Button>

上外表:

WPF开发技巧之花式控件功能扩展详解

No2. 重写控件

很多情况下,并不是把控件换个外貌就可以解决的,我们不仅要改变外貌,还要改变控件的功能,比如说我们经常用的TextBox控件,正常的功能就是用来输入的,但更人性化点,我们想要TextBox能告诉我们当前的文本框应该输入用户名呢,还是地址呢等等。其实这个就是我们经常看到的水印功能,水印文字肯定要能按需设置,那我们不可能简单的通过改变下控件模板就可以解决的。

通过需求我们知道,新的带水印的文本框,至少有个水印这么个依赖属性,供外部设置。当然新的带水印文本框和TextBox大概的样子差不多,但我们也要为新的控件定义外貌。

所以第一步,先定义控件的功能,上代码:


public class WaterMarkTextBox : TextBox
   {
       static WaterMarkTextBox()
       {
           DefaultStyleKeyProperty.OverrideMetadata(typeof(WaterMarkTextBox), new FrameworkPropertyMetadata(typeof(WaterMarkTextBox)));
       }

public string WaterMark
       {
           get { return (string)GetValue(WaterMarkProperty); }
           set { SetValue(WaterMarkProperty, value); }
       }

// Using a DependencyProperty as the backing store for WaterMark.  This enables animation, styling, binding, etc...
       public static readonly DependencyProperty WaterMarkProperty =
           DependencyProperty.Register("WaterMark", typeof(string), typeof(WaterMarkTextBox), new PropertyMetadata(null));

}

第二步,再定义控件的样子,上代码:


<Style TargetType="{x:Type local:WaterMarkTextBox}">
       <Setter Property="BorderBrush" Value="Black"/>
       <Setter Property="BorderThickness" Value="1"/>
       <Setter Property="VerticalContentAlignment" Value="Center"/>
       <Setter Property="Template">
           <Setter.Value>
               <ControlTemplate TargetType="{x:Type local:WaterMarkTextBox}">
                   <Grid>
                       <Border Background="{TemplateBinding Background}"
                               BorderBrush="{TemplateBinding BorderBrush}"
                               BorderThickness="{TemplateBinding BorderThickness}">
                       </Border>
                       <ScrollViewer x:Name="PART_ContentHost"
                                     Grid.Column="0"
                                     Margin="0"
                                     Padding="{TemplateBinding Padding}"
                                     VerticalAlignment="Stretch"
                                     Background="{x:Null}"
                                     BorderThickness="0"
                                     IsTabStop="False"
                                     SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                       <TextBlock x:Name="PART_Message"
                                  Margin="4 0"
                                  Padding="{TemplateBinding Padding}"
                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                  VerticalAlignment="Center"
                                  Foreground="Gray"
                                  Text="{TemplateBinding WaterMark}"
                                  Visibility="Collapsed" />
                   </Grid>
                   <ControlTemplate.Triggers>
                       <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="">
                           <Setter TargetName="PART_Message" Property="Visibility" Value="Visible" />
                       </DataTrigger>
                   </ControlTemplate.Triggers>

</ControlTemplate>
           </Setter.Value>
       </Setter>
   </Style>

请注意Name为PART_Message的TextBlock就是用来呈现水印提示消息的,同时加了个触发器,实现这样的功能:如果未输入任何内容,则显示水印,否则就隐藏水印。

控件使用,上代码:


<local:WaterMarkTextBox Margin="20,0,0,0" Width="200" Height="50" WaterMark="Please Input your name"/>

上效果:

WPF开发技巧之花式控件功能扩展详解

No3. 附加属性来试试

重写控件看似很完美了,真的是这样吗?

好了,我的需求又来了,现在文本框提示很perfect,可是我的密码框PasswordBox也要搞个水印啊?怎么办?再重写个带水印的密码框?此时有没有做相同事情的感觉?作为合格的码农,我们还是要牢记码农界的警世名言:Don't Repeat Yourself!

此时请回忆下WPF的经典知识点:

控件A放到Grid中,A要支持设置行和列,控件B放到Grid中,B也要支持设置行和列。教程中已经告诉我们不要傻不拉几在A和B中都去定义行和列的属性,否则后续C、D......没完没了。

此时就是我们应用附加属性的时候了,在Grid中定义统一的行和列的附加属性,然后附加应用到A、B上就可以了。

反过来看看我们现在的需求,是不是一样的套路?我是不是在个公共的地方定义个水印WaterMark附加属性,然后分别应用到文本框和密码框就可以了?说对了一半,因为我们文本框和密码框老的外表没有显示水印的地方,所以我们同时还要重新定义下他们的新外表。

话不多说,先上附加属性定义的代码:


public class WaterMarkHelper
   {
       public static string GetWaterMark(DependencyObject obj)
       {
           return (string)obj.GetValue(WaterMarkProperty);
       }

public static void SetWaterMark(DependencyObject obj, string value)
       {
           obj.SetValue(WaterMarkProperty, value);
       }

public static readonly DependencyProperty WaterMarkProperty =
           DependencyProperty.RegisterAttached("WaterMark", typeof(string), typeof(WaterMarkHelper), new PropertyMetadata(null));

}

上TextBox新的样式:


<Style x:Key="TextBoxWithWaterMark" TargetType="{x:Type TextBox}">
       <Setter Property="BorderBrush" Value="Black"/>
       <Setter Property="BorderThickness" Value="1"/>
       <Setter Property="VerticalContentAlignment" Value="Center"/>
       <Setter Property="Template">
           <Setter.Value>
               <ControlTemplate TargetType="{x:Type TextBox}">
                   <Grid>
                       <Border Background="{TemplateBinding Background}"
                               BorderBrush="{TemplateBinding BorderBrush}"
                               BorderThickness="{TemplateBinding BorderThickness}">
                       </Border>
                       <ScrollViewer x:Name="PART_ContentHost"
                                     Grid.Column="0"
                                     Margin="0"
                                     Padding="{TemplateBinding Padding}"
                                     VerticalAlignment="Stretch"
                                     Background="{x:Null}"
                                     BorderThickness="0"
                                     IsTabStop="False"
                                     SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                       <TextBlock x:Name="PART_Message"
                                  Margin="4 0"
                                  Padding="{TemplateBinding Padding}"
                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                  VerticalAlignment="Center"
                                  Foreground="Gray"
                                  Text="{TemplateBinding local:WaterMarkHelper.WaterMark}"
                                  Visibility="Collapsed" />
                   </Grid>
                   <ControlTemplate.Triggers>
                       <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="">
                           <Setter TargetName="PART_Message" Property="Visibility" Value="Visible" />
                       </DataTrigger>
                   </ControlTemplate.Triggers>

</ControlTemplate>
           </Setter.Value>
       </Setter>
   </Style>

请和自定义控件中的样式对比下,其实就是PART_Message对应控件的Text绑定的不一样了,这样绑定的是TextBox的附加属性WaterMarkHelper.WaterMark

上应用代码:


<TextBox Style="{StaticResource TextBoxWithWaterMark}" Margin="20,0,0,0" Width="200" Height="50" local:WaterMarkHelper.WaterMark="Please Input your name"/>

请注意,这样要手动明确应用定义的样式资源!

上效果:

WPF开发技巧之花式控件功能扩展详解 

课后作业:请依葫芦画瓢,实现PasswordBox的水印功能 😆

总结

除了这里列举的三种方式,其实还可以通过Behavior行为功能,扩展一个控件的功能,比如著名的拖拽功能!写到这里,我想总结的是:工欲善其事必先利其器!

当我们基础扎实之后,我们真的可以跳出栅栏,灵活应用!

来源:https://www.cnblogs.com/liuww/p/15021857.html

标签:wpf,控件
0
投稿

猜你喜欢

  • springboot整合quartz项目使用案例

    2023-02-13 19:57:12
  • Flutter 使用fluro的转场动画进行页面切换

    2023-06-17 11:49:26
  • spring boot打包成可执行jar包

    2022-09-26 12:48:14
  • C#使用IComparer自定义List类实现排序的方法

    2021-06-28 04:28:09
  • 使用Log4j为项目配置日志输出应用详解以及示例演示的实现分析

    2022-07-30 14:26:45
  • 利用Jetpack Compose实现主题切换功能

    2023-03-10 03:14:37
  • C#利用QrCode.Net生成二维码(Qr码)的方法

    2023-11-30 17:37:38
  • 一文让你搞懂如何手写一个redis分布式锁

    2023-11-29 02:46:30
  • ViewPager判断是向左划还是右划的实例

    2023-12-10 04:53:18
  • java动态线程池的简单实现思路

    2023-10-18 15:53:46
  • android动态加载布局文件示例

    2023-12-10 20:42:46
  • 详解Java动态字节码技术

    2022-06-20 03:20:20
  • Java读取properties文件之中文乱码问题及解决

    2021-07-19 06:43:06
  • 使用SpringMVC响应json格式返回的结果类型

    2022-06-29 20:29:46
  • c#和javascript函数相互调用示例分享

    2023-10-08 21:33:52
  • C# 中const,readonly,static的使用小结

    2022-05-16 20:39:58
  • Android使用TransitionDrawable渐变切换多张图片

    2023-01-08 16:27:22
  • 关于java的九个预定义Class对象

    2023-08-16 01:56:16
  • 深入Android MediaPlayer的使用方法详解

    2023-07-21 21:50:47
  • android实现拖拽裁剪功能

    2022-03-10 09:37:14
  • asp之家 软件编程 m.aspxhome.com