创建自定义可绑定WPF控件的正确方法
我想询问正确的方法,是否要创建由两个控件组成的Bindable用户控件.我不确定自己在做什么-是否正确执行操作,因为遇到了一些问题.
I want to ask about the right way if I want to create Bindable user control consisting of two controls. I am not sure about what I am doing - whether I do it correctly , because I run into some problems.
这就是我想要做的:
让我们将此控件称为ucFlagControl.创建新的自定义用户控件...其目的是显示变量(布尔类型)中逻辑(真/假)值的颜色解释.
Lets call this control ucFlagControl . Create new , custom user control ... Its purpose is to show Color interpretation of logic ( True/ False ) value in variable , type of Bool.
我以前经常做的是使用 Rectangle
,然后使用 Converter 将
FillProperty
绑定到 bool
ean值代码>
What I used to do before was that I use Rectangle
, and Bind FillProperty
to bool
ean value using Converter
我要做的是,我做了一个usercontrol,并在其中放置了矩形和标签而不是我添加以下代码:
What I did to make it works was , that I made a usercontrol , and put rectangle and label inside than I added this code:
public partial class ucStatusFlag : UserControl
{
public ucStatusFlag()
{
InitializeComponent();
}
public string LabelContent
{
get { return (string)GetValue(LabelContentProperty); }
set
{
SetValue(LabelContentProperty, value);
OnPropertyChanged("LabelContent");
}
}
///in case that I use integer or array
public int BitIndex
{
get { return (int)GetValue(BitIndexProperty); }
set
{
SetValue(BitIndexProperty, value);
OnPropertyChanged("BitIndex");
}
}
public string BindingSource
{
get { return (string)GetValue(BindingSourceProperty); }
set
{
SetValue(BindingSourceProperty, value);
OnPropertyChanged("BindingSource");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
/// <summary>
/// Identified the Label dependency property
/// </summary>
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register("LabelContent", typeof(string), typeof(ucStatusFlag), new PropertyMetadata("LabelContent"));
public static readonly DependencyProperty BitIndexProperty =
DependencyProperty.Register("BitIndex", typeof(int), typeof(ucStatusFlag), new PropertyMetadata(0));
public static readonly DependencyProperty BindingSourceProperty =
DependencyProperty.Register("(BindingSource", typeof(string), typeof(ucStatusFlag), new PropertyMetadata(""));
private void StatusFlag_Loaded(object sender, RoutedEventArgs e)
{
if (BindingSource.Length > 0)
{
Binding bind = new Binding();
string s = LabelContent;
int i = BitIndex;
bind.Converter = new StatusToColor();
bind.Path = new PropertyPath(BindingSource);
bind.ConverterParameter = BitIndex.ToString();
bind.Mode = BindingMode.OneWay;
bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
recStatusBit.SetBinding(Rectangle.FillProperty, bind);
}
}
private class StatusToColor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
byte bDataWordIdx;
byte bDataBitIdx;
Byte.TryParse((string)parameter, out bDataBitIdx);
if (Object.ReferenceEquals(typeof(UInt16[]), value.GetType()))
{
UInt16[] uiaData = (UInt16[])value;
bDataWordIdx = (byte)uiaData[0];
if ((uiaData[bDataBitIdx / 16] >> (bDataBitIdx % 16) & 0x1) == 1)
{
return Brushes.Green;
}
else
{
return Brushes.Red;
}
}
else if (Object.ReferenceEquals(typeof(UInt16), value.GetType()))
{
UInt16 uiaData = (UInt16)value;
if (((uiaData >> bDataBitIdx) & 0x1) == 1)
{
return Brushes.Green;
}
else
{
return Brushes.Red;
}
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return 0;
}
}
}
}后来我意识到我可以轻松地绑定内容,而不必创建公共静态只读DependencyProperty LabelContentProperty
}
Than I realized that I can easily bind content and I do not have to create public static readonly DependencyProperty LabelContentProperty
但只是财产
public new string Content
{
get { return (string)label.Content; }
set
{
SetValue(label.Content, value);
OnPropertyChanged("Content");
}
}
这会覆盖原始内容,因此我可以在上层中绑定和/或分配标签的文本-例如放置此用户控件的MainWindow.xaml
this overrides the original content so I am able to Bind and/or assign the text of the label in upper level - in e.g. MainWindow.xaml where this user control is put
第一个问题是,在这种情况下是否还可以,或者是否存在我不知道的背景知识,我也应该以不同的方式使用这种小的控件-我想制作dll.从它加载到工具箱-我测试了它的工作原理.并且比在堆栈面板中使用它要好得多.
First question is if this is in this case OK or if there is some background I am not aware of and I should even such small controls do in different way - I would like to make dll. from it an load it to toolbox - I tested it works. And than use it in for example stack panel .
第二个问题是我在使用矩形"填充"
属性时遇到了问题.我无法像绑定内容那样绑定该属性.我知道矩形是从Shape类派生的,所以我不确定是否与此有关.
Second question is that I have problem with a rectangle "Fill"
property . I am not able to bind that property like I bind content .
I know that the rectangle is derived from Shape class so I am not sure if it has something to do with this.
如果我能够进行与
Content
我可以删除转换器,然后将其绑定到例如MainWindow.xaml文件(使用converter和converter参数)
I can remove the converters than and just bind it in e.g. MainWindow.xaml file (using the converter and converter parameter )
但是 FillProperty
对我不起作用,所以我不确定我的观点.
But FillProperty
does not work for me so I am not sure about my point of view .
谢谢你的建议
很好,很抱歉,但是我没能在下面的评论中完全听到您想说的一切.您能详细解释一下吗?我知道上面的代码不是正确的方法……?或者,您可以发表任何有关它的文章吗?我的实际代码是这样的:在用户控件中...我从后面的代码中删除了所有代码...
well I am sorry but I did not catch all you want to say in a comment below. Could you please explain closer ? I know that the code above is not the right way to do it ... ? Or can you post any article about it ? my actual code is like this: In a user control ... I removed all the code from code behind ...
' <Label x:Name="lStatusBit" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" Margin="2,1,17,2" />
<Rectangle x:Name="recStatusBit" Margin="0,3,1,7" />'
内容属性有效,我看不到Rectangle和矩形fill属性...另一个问题是,如果我在放置uc的XAML中填充Content属性,则Rectangle消失.
Content property works, I cant see Rectangle , and rectangle fill property ... Other problem is if I fill in Content property in XAML where my uc is placed , Rectangle disappears .
我知道我晚了一年,但是如果有人遇到这个问题,我会回答.
I know I'm a year late to the party, but I'll answer incase anyone else comes across this.
-
如果要显示纯文本,则应使用
TextBlock
控件而不是Label
控件.标签具有一个content元素,该内容比TextBlock的简单Text
属性重新渲染/计算的次数更多.
You should use a
TextBlock
control instead ofLabel
controls if you want to display pure text. Labels have a content element which is re-rendered/computed many more times than a TextBlock's simpleText
property.
您应避免使用魔术弦,例如"LabelContent"
.引用属性名称时,应使用C# nameof()
表达式.例如:
You should avoid using magic strings, e.g. "LabelContent"
. You should use the C# nameof()
expression when referencing property names. For example:
我使用lambda表达式稍微整理了一下代码,但这只是首选项.
public string LabelContent
{
get => (string)GetValue(LabelContentProperty);
set => SetValue(LabelContentProperty, value);
}
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register(
nameof(LabelContent),
typeof(string),
typeof(ucStatusFlag),
new PropertyMetadata("Default Value"));
这将防止由于文本输入错误而导致的运行时错误,使您可以跳转到属性的引用,使重构更容易,并且通过提供易于发现的编译错误(如果属性不容易,则可以简化调试).存在).
This will prevent runtime errors due to mistyped text, will allow you to jump to the property's reference, will make refactoring easier, and will make debugging easier by giving you a compile error that's easy to find (if the property doesn't exist).
- 我认为您不需要矩形.如果您只是想更改文本区域的背景色,则可以使用DataTrigger或制作一个转换器.
DataTrigger示例
DataTrigger Example
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<!-- The default value -->
<Setter Property="Background" Value="Transparent" />
<!-- Your trigger -->
<Style.Triggers>
<DataTrigger Binding="{Binding SomeBooleanValue}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
DataTrigger是一种通过绑定到ViewModel上的属性来对控件进行样式设置的快速简便的方法(假设您使用的是MVVM结构),但是存在一些弊端-例如在不同的View上重用相同的样式ViewModel的属性不同.您将不得不再次重写整个样式.
A DataTrigger is a quick and easy way to style a control by binding to a property on your ViewModel (assuming you're using the MVVM structure), but there are some cons - like reusing the same style on a different View whose ViewModel's properties are different. You'd have to rewrite the entire styling again.
让我们将其变成可重复使用的控件,我们可以(1)指定突出显示的背景色,(2)使用布尔值确定控件是否突出显示.
Lets turn it into a reusable control where we can (1) specify a highlight background color, and (2) use a boolean to determine whether the control is highlighted.
我将模板控件放在一个单独的C#类文件中,然后将控件的样式放在另一个单独的资源字典文件中,而不使用UserControl.
I make my templated controls in a separate C# class file and put the control's styling in another separate resource dictionary file instead of using a UserControl.
- 这些模板化控件可以由其他几个控件组成,以使单个 可重用 控件.
- 据我了解,UserControl旨在使用多个模板控件(例如TextBox)并将其交互链接在一起以执行特定方式.
- 我认为这些控件不能在单独的不相关项目中重用-它们会根据您的ViewModel视情况显示数据.
- 如果您将来想通过继承扩展自定义控件,则使用UserControl会很困难.
这是我在解决方案资源管理器中的一些控件的外观:解决方案代码段
Here's what a few of my controls look like in the solution explorer: Solution Files Snippet
- 代码段中的ExpansionPanel控件是具有其他功能/属性的扩展器.
- NavButton是一个具有附加功能/属性的按钮.
- 我有一个NavigationView UserControl,它使用这两个控件来创建比模板控件大得多的内容.
听起来您想创建一个可重用模板控件.
It sounds like you want to create a reusable templated control.
以下是基本步骤:
- 创建一个"主题"文件夹位于项目的根目录.它必须是项目的根源,并且拼写确实很重要.
- 在主题"目录中创建一个 Generic.xaml 资源字典文件.文件夹.它必须直接在主题"标签下文件夹和拼写有用很重要.
- Create a "Themes" folder at the root of your project. It must be at the root of your project and spelling does matters.
- Create a Generic.xaml Resource Dictionary file in the "Themes" folder. It must be directly under the "Themes" folder and spelling does matters.
- 在这里存储自定义控件的默认主题.
- 将自定义控件模板添加到项目时,控件的模板样式将自动添加到Generic.xaml文件中.
<Style TargetType="{x:Type local:Example}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Example}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
- 个人而言,我希望每个控件都有一个单独的.xaml文件,然后将其合并到Generic.xaml资源字典中.这只是出于组织目的.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<!-- Control template styles -->
<ResourceDictionary Source="pack://application:,,,/Themes/ExpansionPanel.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/NavButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextDocument.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextDocumentToolBar.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextEditor.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/HighlightTextBlock.xaml" />
<!-- etc... -->
</ResourceDictionary.MergedDictionaries>
<!-- Other styles or whatever -->
</ResourceDictionary>
- 请务必注意,如果您有依赖于其他控件的控件,顺序确实很重要.
- 将Generic.xaml文件合并到您的App.xaml文件中.
<Application>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Other resource dictionaries... -->
<ResourceDictionary Source="pack://application:,,,/Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Other resource dictionaries... -->
</ResourceDictionary>
</Application.Resources>
</Application>
- 为什么不直接将控件模板合并到App.xaml文件中?WPF直接为自定义类型主题查找Generic.xaml文件.App.xaml也是特定于应用程序的,如果您将该库用作控件库,则将无法在其他应用程序中使用.
- 使用内置的自定义控件模板或标准C#类文件创建.cs文件.
您控件的.cs文件类似于......
Your control's .cs file would resemble something similar to...
public class HighlightTextBlock : Control
{
#region Private Properties
// The default brush color to resort back to
public Brush DefaultBackground;
#endregion
static HighlightTextBlock()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(HighlightTextBlock), new FrameworkPropertyMetadata(typeof(HighlightTextBlock)));
}
// Get the default background color and set it.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
DefaultBackground = Background;
}
#region Dependency Properties
/// <summary>
/// The text to display.
/// </summary>
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
nameof(Text), typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(string.Empty));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
/// <summary>
/// Whether or not the background should be highlighted.
/// </summary>
// This uses a callback to update the background color whenever the value changes
public static readonly DependencyProperty HighlightProperty = DependencyProperty.Register(
nameof(Highlight), typeof(bool),
typeof(HighlightTextBlock), new PropertyMetadata(false, HighlightPropertyChangedCallback));
public bool Highlight
{
get => (bool)GetValue(HighlightProperty);
set => SetValue(HighlightProperty, value);
}
/// <summary>
/// The highlight background color when <see cref="Highlight"/> is true.
/// </summary>
public static readonly DependencyProperty HighlightColorProperty = DependencyProperty.Register(
nameof(HighlightColor), typeof(Brush),
typeof(HighlightTextBlock), new PropertyMetadata(null));
public Brush HighlightColor
{
get => (Brush)GetValue(HighlightColorProperty);
set => SetValue(HighlightColorProperty, value);
}
#endregion
#region Callbacks
// This is the callback that will update the background
private static void HighlightPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var target = (HighlightTextBlock)dependencyObject;
if (target.Highlight)
target.Background = target.HighlightColor;
else
target.Background = target.DefaultBackground;
}
#endregion
}
- 创建ResourceDictionary.xaml文件以存储控件的模板和样式,或直接将其添加到Generic.xaml中.
您的.xaml文件看起来类似于...
Your .xaml file would look something like...
<Style x:Key="HighlightTextBlock" TargetType="{x:Type ctrl:HighlightTextBlock}">
<!-- Default setters... -->
<!-- Define your control's design template -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ctrl:HighlightTextBlock}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<!--
I only bound the Text and Background property in this example
Make sure to bind other properties too.. like Visibility, IsEnabled, etc..
-->
<TextBlock Text="{TemplateBinding Text}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--
Set the default style for the control
The above style has a key, so controls won't use that style
unless the style is explicitly set.
e.g.
<ctrl:HighlightTextBlock Style={StaticResource HighlightTextBlock} />
The reason I used a key above is to allow extending/reusing that default style.
If a key wasn't present then you wouldn't be able to reference it in
another style.
-->
<Style TargetType="{x:Type ctrl:HighlightTextBlock}" BasedOn="{StaticResource HighlightTextBlock}" />
像在步骤2的代码片段中一样,在Generic.xaml中添加对控件资源字典的引用.
Add a reference to the control's resource dictionary in Generic.xaml, like in step 2's code snippet.
用法:
我将 IsChecked
属性绑定到ViewModel上的 IsHighlighted
属性.您可以将其绑定到任何东西.
I'm binding the IsChecked
property to a IsHighlighted
property on my ViewModel.
You can bind it to whatever.
<StackPanel>
<ToggleButton IsChecked="{Binding IsHighlighted}" Content="{Binding IsHighlighted}"
Width="100" Height="35" Margin="5"/>
<ctrl:HighlightTextBlock Background="Transparent" HighlightColor="Red"
Text="HELLO WORLD!!!" Highlight="{Binding IsHighlighted}"
Width="100" Height="35" HorizontalAlignment="Center" />
</StackPanel>
- 您的控件可能看起来有些不同-我使用的是自定义深色主题.