原文:工作记录--WPF自定义控件,实现一个可设置编辑模式的TextBox

1. 背景

  因为最近在使用wpf开发桌面端应用,在查看页面需要把TextBox和Combox等控件设置为只读的。原本是个很简单的事,设置属性IsReadOnly="True"或IsEnabled="False"就可以解决了,可是产品觉得样式不是他想要的(背景是灰色的),想要实现的效果是和编辑时的样式一致,仅仅是不可编辑而已。我想这也简单啊,强制修改背景色和字体就完事了,结果发现TextBox修改背景色是可行的,但是修改后字体是灰色的,改也改不了,Combox更是连背景色都改不了。。。wtf

  于是开始了一段Google之路,发现别人说的都好复杂,大概明白了修改自带的不可编辑或只读的样式不是一件简单的事。于是我准备用一种变通的方法来解决这个问题,写个自定义控件,通过设置自定义控件的属性来显示/隐藏内部控件的方式。

2. 实现

  首先新建一个用户控件(UserControl),命名为LabelRenderTextBox,在LabelRenderTextBox.xaml文件编写以下内容 

   <!--DataContext="{Binding RelativeSource={RelativeSource self}} datacontext绑定自身会破坏binding链,而通过第一个child来binding则不会破坏"-->
  <StackPanel x:Name="panel" DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:LabelRenderTextBox}}">
<Border BorderThickness="1" BorderBrush="#ccc" x:Name="label" Width="{Binding ElementName=panel, Path=ActualWidth}" Height="24" Visibility="Collapsed" Padding="5,3,0,0">
<TextBlock Text="{Binding Text}" ></TextBlock>
</Border>
<TextBox x:Name="textbox" Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="{Binding ElementName=panel, Path=ActualWidth}" Height="24"
base:CustomizeProperty.Placeholder="{Binding Placeholder}" TextChanged="Textbox_TextChanged" ></TextBox>
</StackPanel>

  如图,panel是UserControl内部最外层的元素,把这里的DataContext设为自身,就可以把内部控件的属性和cs文件中的属性进行绑定。label是默认隐藏的元素,textbox是默认显示的控件。label和textbox的Text属性都是绑定的LabelRenderTextBox的Text属性,宽度都是和最外层的panel的宽度进行绑定的,以便于使用者自己设置宽度。

  那么当我们使用LabelRenderTextBox控件进行编辑的时候,实际上还是通过TextBox来实现了,因为设置了Mode=TwoWay进行了双向绑定,UpdateSourceTrigger=PropertyChanged,所以当TextBox的Text改变时,LabelRenderTextBox的Text值也会跟着改变,所以我们可以在LabelRenderTextBox.cs文件里添加一个属性 public string Text { get; set; } ,可是当我们在外部直接修改LabelRenderTextBox的Text值如何使内部的TextBox值也跟着改变呢?这时候就需要用到wpf的依赖属性了,首先通过查看UserControl的继承关系可以发现UserControl是继承自DependencyObject的,意味着它是一个依赖对象,可以设置依赖属性。所以我们可以在LabelRenderTextBox.cs文件里添加以下代码:

       public string Text { get
{
return (string)this.GetValue(TextProperty);
}
set
{
this.SetValue(TextProperty, value);
}
} public static readonly DependencyProperty TextProperty =
DependencyProperty.RegisterAttached("Text", typeof(string), typeof(LabelRenderTextBox), new PropertyMetadata("", OnTextChanged)); private static void OnTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as LabelRenderTextBox;
if (element != null)
{
element.Text = e.NewValue as string;
}
}

  通过DependencyProperty.RegisterAttached方法注册一个Text属性,string类型的,类型为LabelRenderTextBox,PropertyMetadata的第一个参数是默认值,第二个参数是一个回调函数(当属性改变时进行回调),回调时通过Text的set方法来调用SetValue属性来设置依赖属性的值并通知与其绑定的属性(想知道如何实现的可以参考:WPF依赖对象(DependencyObject) 实现源码,理解WPF原理必读)。

  接下来,定义一个枚举类来列举出需要的编辑模式,我这里列举出4种模式,可根据自己的需求进行调整。

    public enum EditModes
{
//编辑模式
Editable = 0,
//只读模式
ReadOnly = 1,
//只读模式展示,单击进入编辑模式,失去焦点后恢复只读模式
Click = 2,
//只读模式展示,双击进入编辑模式,失去焦点后恢复只读模式
DoubleCLick = 3
}

  定义了枚举之后就要使用了,和设置Text属性的方式来注册一个EditMode依赖属性,SetEditable方法传入true/false来设置textbox(编辑)和label(只读)的显示和隐藏。这样只要通过设置EditMode属性就可以完美实现产品的需求了,我真棒!

       #region EditModeProperty
public EditModes EditMode {
get { return (EditModes)this.GetValue(EditModeProperty); }
set {
this.SetValue(EditModeProperty, value);
if (value == EditModes.Editable)
{
SetEditable(true);
}
else
{
SetEditable(false);
}
}
} public static readonly DependencyProperty EditModeProperty =
DependencyProperty.RegisterAttached("EditMode", typeof(EditModes), typeof(LabelRenderTextBox), new PropertyMetadata(EditModes.Editable, OnEditModeChanged)); private static void OnEditModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as LabelRenderTextBox;
if (element != null)
{
element.EditMode = (EditModes)e.NewValue;
}
}
#endregion private void SetEditable(bool editable)
{
if (editable)
{
this.label.Visibility = Visibility.Collapsed;
this.textbox.Visibility = Visibility.Visible;
}
else
{
this.label.Visibility = Visibility.Visible;
this.textbox.Visibility = Visibility.Collapsed;
}
}

  可是按照产品的需求只要Editable和ReadOnly两个属性就够了,其他两个是用来扩展的,可以在合适的场景下带来更好的用户体验,默认展示只读模式,单击或者双击变为编辑模式,失去焦点后恢复只读模式。实现方法也很简单,直接在UserControl定义几个事件,在对应的事件函数进行如下处理。

       private void Click(object sender, MouseButtonEventArgs e)
{
if (EditMode == EditModes.Click)
SetEditable(true);
} private void DoubleClick(object sender, MouseButtonEventArgs e)
{
if (EditMode == EditModes.DoubleCLick)
SetEditable(true);
} private void Lost_Focus(object sender, RoutedEventArgs e)
{
if(EditMode == EditModes.DoubleCLick || EditMode == EditModes.Click)
SetEditable(false);
}

  你可能会想,如果想监听TextBox的事件该怎么做呢,也很简单,在cs文件里定义相同的事件,然后在cs文件里监听对应事件,触发时调用定义的事件即可。

        public event TextChangedEventHandler TextChanged;

        private void Textbox_TextChanged(object sender, TextChangedEventArgs e)
{
this.TextChanged?.Invoke(this, e);
}

  是不是很简单呢?希望可以帮到遇到相同问题的朋友,完整代码如下:

<UserControl x:Class="YZ.HIS.UserControls.LabelRenders.LabelRenderTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:base="clr-namespace:APP_YFT.Base;assembly=APP-YFT.Base"
xmlns:local="clr-namespace:YZ.HIS.UserControls.LabelRenders"
mc:Ignorable="d" BorderThickness="0"
Height="24" d:DesignWidth="80" PreviewMouseLeftButtonDown="Click" PreviewMouseDoubleClick="DoubleClick"
LostFocus="Lost_Focus">
<UserControl.Resources>
<Style x:Key="mySearchTextBoxStyle" TargetType="{x:Type TextBox}">
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="6,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="bdRoot"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Background="{TemplateBinding Background}">
<DockPanel LastChildFill="True">
<Button x:Name="ClearButton"
DockPanel.Dock="Right"
Focusable="False" Click="ExecCleanSearchText"
Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"
FontSize="{TemplateBinding FontSize}">
<Button.Template>
<ControlTemplate>
<Image x:Name="Image" Source="/YZ.HIS;component/Images/tbClean_icon.png" Stretch="None"/>
</ControlTemplate>
</Button.Template>
</Button>
<ScrollViewer x:Name="PART_ContentHost" DockPanel.Dock="Left" Background="{TemplateBinding Background}"/>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="">
<Setter TargetName="ClearButton" Property="Visibility" Value="Collapsed" />
</DataTrigger>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="bdRoot" Property="BorderBrush" Value="#569DE5"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bdRoot" Property="BorderBrush" Value="#7EB4EA"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<!--DataContext="{Binding RelativeSource={RelativeSource self}} datacontext绑定自身会破坏binding链,而通过第一个child来binding则不会破坏"-->
<StackPanel x:Name="panel" DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:LabelRenderTextBox}}">
<Border BorderThickness="1" BorderBrush="#ccc" x:Name="label" Width="{Binding ElementName=panel, Path=ActualWidth}" Height="24" Visibility="Collapsed" Padding="5,3,0,0">
<TextBlock Text="{Binding Text}" ></TextBlock>
</Border>
<TextBox x:Name="textbox" Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="{Binding ElementName=panel, Path=ActualWidth}" Height="24"
base:CustomizeProperty.Placeholder="{Binding Placeholder}" TextChanged="Textbox_TextChanged" ></TextBox>
</StackPanel>
</UserControl>

xaml

using APP_YFT.Base;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes; namespace YZ.HIS.UserControls.LabelRenders
{
/// <summary>
/// LabelRenderTextBox.xaml 的交互逻辑
/// </summary>
public partial class LabelRenderTextBox : UserControl
{
public LabelRenderTextBox()
{
InitializeComponent();
} #region events
public event TextChangedEventHandler TextChanged; private void Textbox_TextChanged(object sender, TextChangedEventArgs e)
{
if (RequiredValue == RequiredValues.Number)
{
TextBox textBox = sender as TextBox;
string text = textBox.Text;
Regex reg = new Regex(@"^[0-9]*$");
if (text == null || (text.Count() == 0) || !reg.IsMatch(text))
{
textBox.Text = "";
return;
}
}
this.TextChanged?.Invoke(this, e);
} //清空输入框事件
private void ExecCleanSearchText(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
if (btn == null)
return;
TextBox tb = btn.TemplatedParent as TextBox;
if (tb != null)
{
tb.Text = "";
}
} private void Click(object sender, MouseButtonEventArgs e)
{
if (EditMode == EditModes.Click)
SetEditable(true);
} private void DoubleClick(object sender, MouseButtonEventArgs e)
{
if (EditMode == EditModes.DoubleCLick)
SetEditable(true);
} private void Lost_Focus(object sender, RoutedEventArgs e)
{
if(EditMode == EditModes.DoubleCLick || EditMode == EditModes.Click)
SetEditable(false);
}
#endregion #region TextProperty
//public string Text { get; set; }
public string Text { get
{
return (string)this.GetValue(TextProperty);
}
set
{
this.SetValue(TextProperty, value);
}
} public static readonly DependencyProperty TextProperty =
DependencyProperty.RegisterAttached("Text", typeof(string), typeof(LabelRenderTextBox), new PropertyMetadata("", OnTextChanged)); private static void OnTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as LabelRenderTextBox;
if (element != null)
{
element.Text = e.NewValue as string;
}
}
#endregion #region PlaceholderProperty
public string Placeholder { get
{
return (string)this.GetValue(PlaceholderProperty);
}
set
{
this.SetValue(PlaceholderProperty, value);
}
} public static readonly DependencyProperty PlaceholderProperty =
DependencyProperty.RegisterAttached("Placeholder", typeof(string), typeof(LabelRenderTextBox), new PropertyMetadata("", OnPlaceholderChanged)); private static void OnPlaceholderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as LabelRenderTextBox;
if (element != null)
{
element.Placeholder = e.NewValue as string;
}
}
#endregion #region RequiredValueProperty
public RequiredValues RequiredValue
{
get { return (RequiredValues)this.GetValue(RequiredValueProperty); }
set
{
this.SetValue(RequiredValueProperty, value);
}
} public static readonly DependencyProperty RequiredValueProperty =
DependencyProperty.RegisterAttached("RequiredValue", typeof(RequiredValues), typeof(LabelRenderTextBox), new PropertyMetadata(RequiredValues.String, OnRequiredValueChanged)); private static void OnRequiredValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as LabelRenderTextBox;
if (element != null)
{
element.RequiredValue = (RequiredValues)e.NewValue;
}
}
#endregion #region UseSearchProperty
public bool UseSearch
{
get { return (bool)this.GetValue(UseSearchProperty); }
set
{
this.SetValue(UseSearchProperty, value);
if (value)
this.textbox.Style = this.FindResource("mySearchTextBoxStyle") as Style; }
} public static readonly DependencyProperty UseSearchProperty =
DependencyProperty.RegisterAttached("UseSearch", typeof(bool), typeof(LabelRenderTextBox), new PropertyMetadata(false, OnUseSearchChanged)); private static void OnUseSearchChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as LabelRenderTextBox;
if (element != null)
{
element.UseSearch = (bool)e.NewValue;
}
}
#endregion #region CaretIndexProperty
public int CaretIndex {
get { return (int)this.GetValue(CaretIndexProperty); }
set
{
this.SetValue(CaretIndexProperty, value);
this.textbox.CaretIndex = value;
}
} public static readonly DependencyProperty CaretIndexProperty =
DependencyProperty.RegisterAttached("CaretIndex", typeof(int), typeof(LabelRenderTextBox), new PropertyMetadata(0, OnCaretIndexChanged)); private static void OnCaretIndexChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as LabelRenderTextBox;
if (element != null)
{
element.CaretIndex = (int)e.NewValue;
}
}
#endregion #region SelectionStartProperty
public int SelectionStart
{
get { return (int)this.GetValue(SelectionStartProperty); }
set
{
this.SetValue(SelectionStartProperty, value);
this.textbox.SelectionStart = value;
}
} public static readonly DependencyProperty SelectionStartProperty =
DependencyProperty.RegisterAttached("SelectionStart", typeof(int), typeof(LabelRenderTextBox), new PropertyMetadata(0, OnSelectionStartChanged)); private static void OnSelectionStartChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as LabelRenderTextBox;
if (element != null)
{
element.SelectionStart = (int)e.NewValue;
}
}
#endregion #region EditModeProperty
public EditModes EditMode {
get { return (EditModes)this.GetValue(EditModeProperty); }
set {
this.SetValue(EditModeProperty, value);
if (value == EditModes.Editable)
{
SetEditable(true);
}
else
{
SetEditable(false);
}
}
} public static readonly DependencyProperty EditModeProperty =
DependencyProperty.RegisterAttached("EditMode", typeof(EditModes), typeof(LabelRenderTextBox), new PropertyMetadata(EditModes.Editable, OnEditModeChanged)); private static void OnEditModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as LabelRenderTextBox;
if (element != null)
{
element.EditMode = (EditModes)e.NewValue;
}
}
#endregion private void SetEditable(bool editable)
{
if (editable)
{
this.label.Visibility = Visibility.Collapsed;
this.textbox.Visibility = Visibility.Visible;
}
else
{
this.label.Visibility = Visibility.Visible;
this.textbox.Visibility = Visibility.Collapsed;
}
} } public enum EditModes
{
//编辑模式
Editable = 0,
//只读模式
ReadOnly = 1,
//只读模式展示,单击进入编辑模式,失去焦点后恢复只读模式
Click = 2,
//只读模式展示,双击进入编辑模式,失去焦点后恢复只读模式
DoubleCLick = 3
} public enum RequiredValues
{
String = 0,
Number = 1
}
}

C#

  

工作记录--WPF自定义控件,实现一个可设置编辑模式的TextBox的更多相关文章

  1. [WPF 自定义控件]开始一个自定义控件库项目

    1. 目标 我实现了一个自定义控件库,并且打算用这个控件库作例子写一些博客.这个控件库主要目标是用于教学,希望通过这些博客初学者可以学会为自己或公司创建自定义控件,并且对WPF有更深入的了解. 控件库 ...

  2. [WPF 自定义控件]自定义一个“传统”的 Validation.ErrorTemplate

    1. 什么是Validaion.ErrorTemplate 数据绑定模型允许您将与您Binding的对象相关联ValidationRules. 如果用户输入的值无效,你可能希望在应用程序 用户界面 ( ...

  3. ES 记录之如何创建一个索引映射,以及一些设置

    ElasticSearch 系列文章 1 ES 入门之一 安装ElasticSearcha 2 ES 记录之如何创建一个索引映射 3 ElasticSearch 学习记录之Text keyword 两 ...

  4. WPF自定义控件(一)の控件分类

    一.什么是控件(Controls) 控件是指对数据和方法的封装.控件可以有自己的属性和方法,其中属性是控件数据的简单访问者,方法则是控件的一些简单而可见的功能.控件创建过程包括设计.开发.调试(就是所 ...

  5. WPF自定义控件与样式(3)-TextBox & RichTextBox & PasswordBox样式、水印、Label标签、功能扩展

    一.前言.预览 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要是对文本 ...

  6. WPF自定义控件与样式(1)-矢量字体图标(iconfont)

    一.图标字体 图标字体在网页开发上运用非常广泛,具体可以网络搜索了解,网页上的运用有很多例子,如Bootstrap.但在C/S程序中使用还不多,字体图标其实就是把矢量图形打包到字体文件里,就像使用一般 ...

  7. WPF自定义控件与样式(2)-自定义按钮FButton

    一.前言.效果图 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 还是先看看效果 ...

  8. WPF自定义控件与样式(15)-终结篇 & 系列文章索引 & 源码共享

    系列文章目录  WPF自定义控件与样式(1)-矢量字体图标(iconfont) WPF自定义控件与样式(2)-自定义按钮FButton WPF自定义控件与样式(3)-TextBox & Ric ...

  9. WPF自定义控件与样式(4)-CheckBox/RadioButton自定义样式

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: Che ...

随机推荐

  1. vue.js 2.0 --- 安装node环境,webpack和脚手架(入门篇)

    1.环境搭建 1.1.安装node.js 1.2 安装过程很简单,一路“下一步”就可以了.安装完成之后,打开命令行工具(win+r,然后输入cmd),输入 node -v,如下图,如果出现相应的版本号 ...

  2. 25 面向对象设计实例——基于PCL点云库的通用工具开发

    0 引言 问题背景:pcl中提供了大量工具,用于对点云和三角面片文件进行处理和显示.在研究中,存在很多简易的需求,比如点云坐标转换,点云的打开显示以及同步显示,点云的最小包络求解,点云的格式转换等等. ...

  3. NX二次开发-UFUN获取工程图视图边界线颜色UF_DRAW_ask_border_color

    #include <uf.h> #include <uf_draw.h> #include <uf_ui.h> UF_initialize(); ; UF_DRAW ...

  4. NX二次开发-UFUN工程图表格注释设置单元格首选项UF_TABNOT_set_cell_prefs

    NX9+VS2012 #include <uf.h> #include <uf_tabnot.h> #include <NXOpen/Part.hxx> #incl ...

  5. 牛客多校第七场 C Governing sand 线段树

    题意: 有一个树林,树林中不同种类的树有不同的数量,高度,砍伐它们的价格.现在要求砍掉一些树,使得高度最高的树占剩下的树的总数的一半以上,求最小花费. 题解: 用线段树维护不同种类树的信息,叶子节点从 ...

  6. 剑指offer——25合并两个排序的链表

    题目描述 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则.   题解: 使用普通方法,或者递归,注意新的头节点即可. //使用普通的合并方法 class S ...

  7. 20140412 iphone不完美越狱 无限黑屏解决

    1.不完美越狱 工具:爱思助手.cydia 方法: 爱思助手刷6.1.3固件 一键越狱->关机越狱 高手工具->不完全越狱引导 进入手机后,打开cydia,下载insomnia防黑屏插件 ...

  8. axios全局拦截响应

    在系统开发过程中,若遇到长时间未操作,则需要将页面跳转到登录页面.因为现在都是前后端分离的开发模式,路由跳转都交给前端,而后端只返回一个报错信息,例如"errorMsg":&quo ...

  9. pandas中series求交集

    在进行数据探索的时候会遇到求交集的情况,比如说:优惠卷预测的时候,有多张表,表1有用户id,表2也有用户id,但是不能确定表1的用户有多少出现在表2当中. un_id1,un_id2 为两个 Seri ...

  10. java oop第07章_集合框架

    一. 什么是集合: 在Java中提供了一些可以保存同一数据类型的数据集称为集合,就是规定了一些集合的规范(接口.抽象类.实现类)及方法, 方便我们程序在保存数据时进行增.删.改.查操作,编程更加高效. ...