如何在不共享接口的不同类型上调用具有相同名称的属性?

时间:2010-10-15 21:20:35

标签: wpf reflection interface dependency-properties itemscontrol

我有一个DataTemplate需要在ItemsControl的容器上设置IsSelected属性(例如TreeViewItem,ListViewItem或ComboBoxItem)。但是,在传递给它之前,它不知道容器的类型。由于IsSelected不是公共基类或接口的一部分,也不是AddOwner向各个类注册的常见依赖属性(Duh,MS !!! WTF不是?!!),我最终得到了这个混乱。

if (container is TreeViewItem) {
    (container as TreeViewItem).IsSelected = true;
    return;
}

if (container is ListBoxItem) {
    (container as ListBoxItem).IsSelected = true;
    return;
}

if (container is ComboBoxItem) {
    (container as ComboBoxItem).IsSelected = true;
    return;
}

...这不仅是冗长的,而且如果我使用不同的使用不同容器类型的ItemsControl,则需要我修改它!不好!<​​/ p>

当然,我可以通过将这个逻辑放在扩展方法(该死的C#,因为没有扩展属性!!)中调用IsContainerSelected和SetContainerSelected并将它们放在UIElement上,然后在上面移动上面的代码来增强它,但它只是制作外面的整洁者。内部仍然是一团糟。

我唯一的另一个想法是使用反射并查找IsSelected属性并使用它,如果找到,但我总是对做这样的事情很谨慎。但是,由于没有通用的接口或基类,我不确定我在这里有一个选择。

对于上下文,我在几个不同的ItemsControl之间共享一个复杂的数据模板,模板本身具有可以接收焦点的控件,如复选框和文本框。但是,当这些控件通过鼠标获得焦点时,基础容器项目不会被选中,之前选择的任何内容仍然是如此。

我的解决方法是使用附加行为,利用预览事件在焦点发生之前拦截焦点,并相应地设置基础项,这在我硬编码TreeViewItem或ListBoxItem等时效果很好,但我不喜欢我不想对类型进行硬编码,因为控件不应该真正关心。这就是崩溃的部分。

唉!!!为什么MS只注册相同的附加属性或者至少创建一个ISelectableContainer接口?!!

3 个答案:

答案 0 :(得分:2)

我已经阅读了你的答案,这确实有意义 - 在你的情况下,IsSelected显然可能是ViewModel的一部分,而且这似乎是你案例中最好的解决方案。

但您要求进一步解释C#动态功能。 C#4.0现在具有一些动态功能,这使我们能够创建只能在Python,Ruby或JavaScript等语言中实现的代码。当然,这有其成本 - dynamic滥用不仅会使代码变慢,而且会更加混乱 - 因为您会丢失编译时错误和智能感知。

我写了一个简单的例子,你可以更好地理解它:

public class ClassOne
{
    public int SameProperty { get; set; }
}

public class ClassTwo
{
    public int SameProperty { get; set; }
}

public class ClassThree
{
    public string SameProperty { get; set; }
}

public partial class Form1 : Form
{
    public Form1() {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e) {
        dynamic wrapper = new ClassOne();
        wrapper.SameProperty = 5;

        wrapper = new ClassTwo();
        wrapper.SameProperty = 15;

        wrapper = new ClassThree();
        wrapper.SameProperty = "Now it is a string!";

        // And now a run-time error...
        wrapper.AnotherProperty = "And this won't work...";

    }
}

正如您所看到的,wrapper没有任何明确的类型 - dynamic引用将允许任何类型的方法或属性调用,因为实际绑定仅在运行时进行,而不是编译时。

当然,这个例子非常幼稚,但有时动态代码可能很有用 - 它是避免显式反射或避免基于类型的长if...else语句的好选择(如上面的代码片段)。

答案 1 :(得分:1)

我不确定我是否完全理解你的问题,但是你可以尝试在模型中添加一个IsSelected布尔值,然后将该属性绑定到它所包含的Item控件。这样,你只需要担心设置模型中的属性,无论容器如何。

答案 2 :(得分:0)

Per @mdm20的答案,他建议修改ViewModel,这当然是你想要做的。然而,这是纯粹与视图相关的问题(与键盘导航相关),并且根本没有反映在ViewModel中,也不应该在这种情况下反映出来。

但这给了我一个想法!由于我正在使用自定义控件来渲染项目,无论哪个项目控件(通过其数据模板)都被添加到该项目中,该控件自然 具有多个实例(所有这些实例都指向同一个实例) ViewModel实例),这就是我想要的!

因此,我不是将IsSelected添加到ViewModel,而是将其添加到用户控件本身,然后我只是绑定到数据模板中的相应ItemsControl,我知道。然后,我可以根据需要在代码隐藏中为用户控件设置IsSelected属性(即在预览鼠标事件期间等),并且基础ItemsControl会做出相应的响应!效果很好并且保持ViewModel清洁,因为模型和视图模型都不需要知道它。 IsSelected仍然纯粹在UI中,在这种特殊情况下它应该是!

相关问题