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>
上外表:
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"/>
上效果:
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"/>
请注意,这样要手动明确应用定义的样式资源!
上效果:
课后作业:请依葫芦画瓢,实现PasswordBox的水印功能 😆
总结
除了这里列举的三种方式,其实还可以通过Behavior行为功能,扩展一个控件的功能,比如著名的拖拽功能!写到这里,我想总结的是:工欲善其事必先利其器!
当我们基础扎实之后,我们真的可以跳出栅栏,灵活应用!
来源:https://www.cnblogs.com/liuww/p/15021857.html


猜你喜欢
- 首先我们要明白一下几点,1.代码写出来除了让他跑起来还有个非常非常重要的作用是维护,因为没有一成不变的代码,需求变化代码就不可避免的要变化。
- 实现流程初始化一定数量的任务处理线程和缓存线程池,用户每次调用接口,开启一个线程处理。假设初始化5个处理器,代码执行 BlockingQue
- mybatis自动生成代码(实体类、Dao接口等)是很成熟的了,就是使用mybatis-generator插件。 它是一个开源的插件,使用m
- 前言毕业季来临,很多小伙伴在忙于考公或者准备研究生复试等工作,因此从网上下载或者购买了源码。源码在本地运行成功之后,想要稍微修改一下,结果发
- Java 中可以使用 java.util.Stream 对一个集合(实现了java.util.Collection接口的类)做各种操作,例如
- 一、导论java技术体系中所提到的内存自动化管理归根结底就是内存的分配与回收两个问题,之前已经和大家谈过java回收的相关知识,今天来和大家
- 在使用jQuery时候,我们经常会看到或者使用到方法链,例如:$("#p1").css("color"
- 这个类(我的是Activity中)继承SensorEventListener接口先获取传感器对象,再获取传感器对象的类型//获取传感器管理对
- 本文实例讲述了Android开发中使用Intent打开第三方应用及验证可用性的方法。分享给大家供大家参考,具体如下:Android中提供了I
- 安全无处不在,趁着放假读了一下 Shiro 文档,并记录一下 Shiro 整合 Spring Boot 在数据库中根据角色控制访问权限简介A
- 网络应用分为客户端和服务端两部分,而Socket类是负责处理客户端通信的Java类。通过这个类可以连接到指定IP或域名的服务器上,并且可以和
- 直接用英文逗号分隔就可以了,比如:inerface IHello { String sayHello(String name);
- using System;using System.Collections.Generic;using System.IO;using Sy
- Spring内置 * 对于 Web 应用来说,ServletContext 对象是唯一的,一个 Web 应用,只有一个ServletCont
- 本文实例为大家分享了Android实现简单垂直进度条的具体代码,供大家参考,具体内容如下代码注释特别清晰,就不多解释了支持属性:progre
- 前言 为什么在kotlin要使用协程呢,这好比去了重庆不吃火锅一样的道理。协程的概念并
- 1.属性驱动 前台表单中字段的name和后台action中的属性字段的名称必须保持一致;2.域驱动 前台表单中字段的name应该为:obje
- 本文实例讲述了Java编程中文件读写的方法。分享给大家供大家参考,具体如下:Java中文件读写操作的作用是什么?回答这个问题时应该先想到的是
- Kotlin中函数都是头等的,这意味着它可以存储在变量与数据结构中、作为参数传递给其他高阶函数以及从其他高阶函数返回。可以向操作任何其他非函
- 1 仿射变换仿射变换:一种二维坐标到二维坐标的线性变换,它保持二维图像的平直性与平行性,即变换后直线依然是直线,平行的线依然平行。packa