创建自定义可绑定WPF控件的正确方法 [英] Proper way to create Custom Bindable WPF Control

查看:74
本文介绍了创建自定义可绑定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 boolean 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.

  1. 如果要显示纯文本,则应使用 TextBlock 控件而不是 Label 控件.标签具有一个content元素,该内容比TextBlock的简单 Text 属性重新渲染/计算的次数更多.

  1. You should use a TextBlock control instead of Label 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 simple Text 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).

  1. 我认为您不需要矩形.如果您只是想更改文本区域的背景色,则可以使用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.

以下是基本步骤:

  1. 创建一个"主题"文件夹位于项目的根目录.它必须是项目的根源,并且拼写确实很重要.
  2. 在主题"目录中创建一个 Generic.xaml 资源字典文件.文件夹.它必须直接在主题"标签下文件夹和拼写有用很重要.
  1. Create a "Themes" folder at the root of your project. It must be at the root of your project and spelling does matters.
  2. 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>
      
      

      • 请务必注意,如果您有依赖于其他控件的控件,顺序确实很重要.
        1. 将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也是特定于应用程序的,如果您将该库用作控件库,则将无法在其他应用程序中使用.
          1. 使用内置的自定义控件模板或标准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
          
          }
          
          

          1. 创建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>
          
          

          在错误的代码段上

          在真实代码段上

          • 您的控件可能看起来有些不同-我使用的是自定义深色主题.

          这篇关于创建自定义可绑定WPF控件的正确方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆