自定义ItemsControl和选择支持

3
我希望能创建一个控件,它具备以下功能:
  • 从ItemsControl派生而来
  • 可以绑定到枚举的列表
  • 对于每个枚举值,它将显示RadioButton
  • 当选中特定的RadioButton时,SelectedItem将包含与该RadioButton相关联的枚举值。
我已经实现了上面列表中的前三个功能,但是在第四个功能上遇到了问题。我怀疑这可能与项容器类的错误实现或generic.xaml中错误的项模板定义有关。
项容器如下所示:
public class MyEnumSelectorItem : ContentControl
{
public static readonly DependencyProperty IsSelectedProperty;

static MyEnumSelectorItem()
{
    IsSelectedProperty = Selector.IsSelectedProperty.AddOwner(typeof(MyEnumSelectorItem));
}

public bool IsSelected
{
    get { return (bool)GetValue(IsSelectedProperty); }
    set { SetValue(IsSelectedProperty, value); }
}

static readonly DependencyProperty ModeProperty = 
    DependencyProperty.Register("Mode", typeof(MyEnum), typeof(MyEnumSelector), new PropertyMetadata());
public MyEnum Mode
{
    get { return (MyEnum)GetValue(ModeProperty); }
    set { SetValue(ModeProperty, value); }
}
}

项目容器类型与选择器控件(派生自System.Windows.Controls.Primitives.Selector)相关联,使用IsItemItsOwnContainerOverride/GetContainerForItemOverride/PrepareContainerForItemOverride方法重写。

generic.xaml中相关的片段如下所示:

<Style TargetType="{x:Type controls:MyEnumSelector}">
    <Setter Property="ItemTemplate">
        <Setter.Value>
            <DataTemplate>
                <RadioButton Content="{Binding}" 
                     IsChecked="{Binding IsSelected, Mode=TwoWay}" 
                     GroupName="enumSelector" Height="25" FontWeight="Bold"  />
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

以上假设正确绑定IsSelected属性将自动导致设置SelectedItem属性(我希望这是正确的,不是吗?)。


你不应该从Selector派生吗? SelectedItem是在Selector上定义的,而不是在ItemsControl上。 - Adi Lester
实际上,它源自 Selector(正如我后来在我的问题中指出的那样)- 在第一句话中,我是指它至少应该源自 ItemsControl。 - Tomasz Grobelny
1个回答

2

在我看来,仅仅调用 Selector.IsSelectedProperty.AddOwner 是不够的。

通过查看ILSpyListBoxItem 的代码,你可以看到可能正在执行其他一些操作来同步所选项和选择器本身,例如触发 Selected/Unselected 事件。

static ListBoxItem()
{
    ListBoxItem.IsSelectedProperty = Selector.IsSelectedProperty.AddOwner(typeof(ListBoxItem), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, new PropertyChangedCallback(ListBoxItem.OnIsSelectedChanged)));
    ListBoxItem.SelectedEvent = Selector.SelectedEvent.AddOwner(typeof(ListBoxItem));
    ListBoxItem.UnselectedEvent = Selector.UnselectedEvent.AddOwner(typeof(ListBoxItem));
    ...
}

private static void OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    ListBoxItem listBoxItem = d as ListBoxItem;
    bool flag = (bool)e.NewValue;
    Selector parentSelector = listBoxItem.ParentSelector;
    if (parentSelector != null)
    {
        parentSelector.RaiseIsSelectedChangedAutomationEvent(listBoxItem, flag);
    }
    if (flag)
    {
        listBoxItem.OnSelected(new RoutedEventArgs(Selector.SelectedEvent, listBoxItem));
    }
    else
    {
        listBoxItem.OnUnselected(new RoutedEventArgs(Selector.UnselectedEvent, listBoxItem));
    }
    listBoxItem.UpdateVisualState();
}

我自己没有尝试过来验证这个是否真正解决了问题,但我认为这是一个不错的起点。你还应该考虑让你的项目从ListBoxItem派生而不是ContentControl


刚刚我尝试将MyEnumSelectorItem派生自ListBoxItem,并单独添加OnIsSelectedChanged回调函数,但不幸的是没有任何改变(且回调函数未被调用)。 - Tomasz Grobelny
如果回调函数没有被调用(并且您记得将其添加到属性的元数据中),那么很有可能绑定出现了问题。您应该查找绑定错误并确保实际设置了值。@TomaszGrobelny - Adi Lester
是的,它被添加到属性元数据中,就像上面一样。我现在才注意到在VS的输出窗口中出现了以下错误:System.Windows.Data Error: 39 : BindingExpression路径错误:'IsSelected'属性在'object''MyEnum' (HashCode=4)'上未找到。BindingExpression:Path=IsSelected; DataItem='MyEnum' (HashCode=4); target element is 'RadioButton' (Name=''); target property is 'IsChecked' (type 'Nullable`1')。看起来我的绑定是指向原始枚举而不是容器项(如调试器所示)...但是我该如何编写正确的绑定呢? - Tomasz Grobelny
@TomaszGrobelny 请尝试使用TemplateBinding而不是Binding - Adi Lester
你的意思是这样的吗: IsChecked="{TemplateBinding IsSelected}"? 这会在编译期间导致"error MC3011: Cannot find the static member 'IsSelectedProperty' on the type 'ContentPresenter'"错误。 - Tomasz Grobelny

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接