总结一下我正在做的事情,我有一个自定义控件,它看起来像一个选中的列表框,并且有两个依赖属性,一个提供可用选项列表,另一个表示组合选择选项的枚举标志值。
正如我提到的,我的自定义控件公开了两个不同的 DependencyProperties,其中一个是名为 Options 的选项列表,另一个名为 SelectedOptions 的属性是使用特定枚举类型的[Flags] 属性允许设置值的组合。然后,我的 UserControl 包含一个类似于 ListBox 的 ItemsControl,用于显示选项和复选框。当选中或取消选中该复选框时,应使用相应的按位运算相应地更新 SelectedOptions 属性。
我遇到的问题是,除了诉诸维护私有字段和处理属性更改事件来更新我的属性之外,我别无选择,这在 WPF 中感觉不自然。我尝试过使用 ValueConverters,但遇到了一个问题,即我无法真正使用与值转换器绑定的绑定,因此我需要将枚举值硬编码为 ValueConverter 参数,这是不可接受的。如果有人看到了如何明智地做到这一点的好例子,我将不胜感激。
旁注:
这也是我过去在尝试理解依赖属性如何不允许计算或延迟值时遇到的一个问题。另一个示例是当人们可能想要将子控件上的属性公开为父控件上的属性时。大多数人建议在这种情况下使用绑定,但只有在子控件属性是依赖属性时才有效,因为放置绑定以使目标是父属性,当父控件的用户想要设置自己的绑定时,它会被覆盖对于该财产。
To summarize what I'm doing, I have a custom control that looks like a checked listbox and that has two dependency properties one that provides a list of available options and the other that represents a enum flag value that combines the selection options.
So as I mentioned my custom control exposes two different DependencyProperties, one of which is a list of options called Options and the other property called SelectedOptions is of a specific Enum type that uses the [Flags] attribute to allow combinations of values to be set. My UserControl then contains an ItemsControl similar to a ListBox that is used to display the options along with a checkbox. When the check box is checked or unchecked the SelectedOptions property should be updated accordingly by using the corresponding bitwise operation.
The problem I'm experiencing is that I have no way other than resorting to maintaining private fields and handling property change events to update my properties which just feels unatural in WPF. I have tried using ValueConverters but have run into the problem that I can't really using binding with the value converter binding so I would need to resort to hard coding my enum values as the ValueConverter parameter which is not acceptable. If anybody has seen a good example of how to do this sanely I would greatly appreciate any input.
Side Note:
This has been a problem I've had in the past too while trying to wrap my head around how dependency properties don't allow calculated or deferred values. Another example is when one may want to expose a property on a child control as a property on the parent. Most suggest in this case to use binding but that only works if the child controls property is a Dependency Property since placing the binding so that the target is the parent property it would be overwritten when the user of the parent control wants to set their own binding for that property.
发布评论
评论(3)
如果不深入查看您的代码,我无法确切确定您想要做什么,但我认为我对您的场景有一个模糊的了解。我为您构建了一个示例,说明了与此类似的内容。为了便于演示,我没有构建新的控件,而是将所有代码放置在一个
Window
中。首先,让我们看一下窗口的 XAML:窗口的 DataContext 设置为其自己的代码隐藏,因此我可以绑定到那里的属性。我有一些属性 -
AvailableOptions
是您可以选择的所有选项。SelectedOptions
是用户当前选择的选项。SelectCommand
是一个RelayCommand
用于向SelectedOptions
添加标志或删除标志。XAML 的其余部分应该非常简单。
ListBox
绑定到所有可用选项,每个选项都表示为单个CheckBox
。请特别注意CommandParameter
,它绑定到选项项本身。现在让我们看一下隐藏的代码,魔法发生的地方:从顶部开始,我们有枚举声明,后面是
SelectedOptions
依赖属性和AvailableOptions
属性(可以是标准 CLR 属性,因为它永远不会改变)。然后我们就有了我们的命令,以及将为该命令执行的处理程序(每当选中或取消选中一个选项时)。首先注意命令是如何连接的 - 我们创建一个新的RelayCommand
并告诉它运行OnSelect
,并传入命令参数。请记住,这与 XAML 中绑定的命令参数相同 - 这意味着它是当前选中或未选中的选项。我们使用按位运算符将该选项与SelectedOptions
进行比较。如果该选项存在,则意味着我们要取消选中它,并且需要使用按位与将其清除。如果它不存在,我们使用按位或将其添加到所选中。发生这种情况时,
SelectedOptions
依赖项属性会自动更新,从而更新 XAML 中的TextBlock
绑定。这是最终结果:I can't be sure exactly what you're trying to do without looking at your code in-depth, but I think I have a vague idea of your scenario. I have constructed an example for you, illustrating something similar to this. Rather than build a new control, I have placed all of the code in a single
Window
, for ease of demonstration. For starters, let's look at the XAML for the window:The window's
DataContext
is set to its own code-behind, so I can bind to properties there. I have a handful of properties-AvailableOptions
is all the options you can choose from.SelectedOptions
are the options that the user has currently selected.SelectCommand
is aRelayCommand
that is used to either add a flag to theSelectedOptions
or remove one.The rest of the XAML should be very straightforward. The
ListBox
is bound to all of the available options, and each option is represented as a singleCheckBox
. Pay careful attention to theCommandParameter
, which is bound to the option item itself. Now let's take a look at the code-behind, where the magic happens:Beginning from the top, we have the enum declaration, followed by the
SelectedOptions
dependency property, and theAvailableOptions
property (which can be a standard CLR property since it will never change). We then have our command, and the handler which will be executed for the command (whenever an option is checked or unchecked). First notice how the command is wired up- we create a newRelayCommand
and tell it to runOnSelect
, passing in the command parameter. Remember this is the same command parameter that was bound in the XAML- that means it is the current option being checked or unchecked. We compare that option to theSelectedOptions
using bitwise operators. If the option exists, that means we are unchecking it and we need to clear it off using a bitwise AND. If it doesn't exist, we add it to selected using a bitwise OR.When that happens, the
SelectedOptions
dependency property is automatically updated, which updates theTextBlock
binding in the XAML. Here is the final result:不同的解决方案
对于这种情况,我使用了一种非常不同的解决方案,我认为它更干净。使用我创建的几个实用程序类,我可以直接绑定 SelectedOptions,而无需编写应用程序代码来处理命令、集合更新等。
EnumExpansion 类
我创建了一个具有以下签名的简单类:
EnumValue可以设置为任何枚举类型。设置 EnumValue 时,通过删除当前 EnumValue 中不存在的所有标志并添加当前 EnumValue 中的所有标志来更新内部 ObservableCollection。每当内部集合发生更改时,EnumValue 就会更新。
BindableSelectedItems 属性
我还创建了一个简单的附加属性,允许 ListBox 绑定其 SelectedItems 属性。它的使用方式如下:
附加属性是通过订阅 ListBox 上的 SelectionChanged 和属性值(类型为 INotifyCollectionChanged)的 CollectionChanged 来实现的。
初始化 SelectedOptionsExpansion
您可以在 XAML 中执行此操作,但在代码中非常简单:
工作原理
枚举到 ListBox:
ListBox 到 Enum:
为什么我更喜欢这个解决方案
创建两个实用程序类后,绑定过程的其余部分就是简单的数据绑定。无需在应用程序代码中处理命令或更新集合 - 它全部隐藏在实用程序类中。
A different solution
I use a very different solution for this situation that I think is much cleaner. Using a couple of utility classes I created I can bind the SelectedOptions directly without having to write application code to deal with commands, collection updates, etc.
EnumExpansion class
I created a simple class with the following signature:
EnumValue can be set to any enum type. When EnumValue is set, the internal ObservableCollection is updated by removing all flags not in the current EnumValue and adding all flags in the current EnumValue. Whenever the internal collection is changed, EnumValue is updated.
BindableSelectedItems property
I also created a simple attached property that allows ListBox to bind its SelectedItems property. It is used like this:
The attached property is implemented by subscribing to SelectionChanged on the ListBox and CollectionChanged on the property value (which is of type INotifyCollectionChanged).
Initializing SelectedOptionsExpansion
You can do this in XAML but it is quite easy in code:
How it works
Enum to ListBox:
ListBox to Enum:
Why I prefer this solution
Once the two utility classes are created, the rest of the binding process is straightforward data binding. There is no need to handle commands or update collections in your application code - it is all hidden within the utility classes.
为了支持默认值的概念,您需要在
CheckBox.IsChecked
属性上设置绑定。您需要当前选项(位于相关复选框的DataContext
上)以及位于窗口上的SelectedOptions
属性。因此,此绑定变为:FlagsToBoolConverter
只需接受这些并检查当前选项是否在SelectedOptions
上:现在尝试设置
SelectedOptions
构造函数中的某个默认值。请注意,相关的CheckBox
会自动选中,并且所有绑定仍然有效。胜利!To support the notion of defaults, you will need to set up a binding on the
CheckBox.IsChecked
property. You need both the current option (which is found on theDataContext
of the relevant checkbox) as well as theSelectedOptions
property, which is located on the window. So this binding becomes:The
FlagsToBoolConverter
simply takes in these and checks to see if the current option is on theSelectedOptions
:Now try setting the
SelectedOptions
to some default value in the constructor. Notice that the relevantCheckBox
is automatically checked, and all the bindings are still functional. Victory!