WPF ItemsControl以Canvas为ItemsPanel时继承Thumb的VM绑定问题咨询
问题根因
你的问题源于WPF ItemsControl对不同类型集合项的差异化处理逻辑:
- 当集合项是普通CLR对象(非UIElement派生)时,ItemsControl会自动生成
ContentPresenter作为项容器,将容器的DataContext设置为对应的CLR对象,ItemContainerStyle的绑定会自动从该CLR对象取值,ItemTemplate也会正常生效。 - 当集合项本身是
UIElement派生类(你继承的Thumb属于Control,是UIElement的子类)时,ItemsControl会直接将该UIElement作为项本身使用,不会生成额外的容器,也不会自动设置它的DataContext为自身,同时ItemTemplate会被直接忽略。
你遇到的两个异常都由此引发:
- 绑定查找MainWindow的Position属性:
ItemContainerStyle的Setter默认绑定源是父级元素的DataContext(也就是MainWindow),没有找到对应属性就抛出错误,导致Canvas.Left/Top赋值失败变为NaN,控件无法正常定位显示。 - 即使定位正确也看不到文本:因为ItemTemplate被忽略,你定义的TextBlock不会被渲染。
解决方案(WidgetVM继承Thumb的前提下)
第一步:补全必要的ItemsPanel设置
首先要确认你的ItemsControl已经指定了面板为Canvas,否则Canvas.Left/Top属性不会生效:
<ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel>
第二步:调整绑定逻辑,有两种可选实现方式
方式1:修改WidgetVM构造函数,手动设置DataContext为自身
在Widgetvm的构造函数中添加一行代码即可兼容你原本的XAML写法:
public Widgetvm() { this.DataContext = this; }
这个方案改动最小,原来的ItemTemplate和ItemContainerStyle都不用修改。
方式2:修改XAML绑定,指定绑定源为控件本身
不需要修改后台代码,直接调整ItemContainerStyle的绑定路径,同时把内容定义放到Style里(因为ItemTemplate会被忽略):
<ItemsControl.ItemContainerStyle> <Style TargetType="local:Widgetvm"> <Setter Property="Canvas.Left" Value="{Binding Position.X, RelativeSource={RelativeSource Self}}"/> <Setter Property="Canvas.Top" Value="{Binding Position.Y, RelativeSource={RelativeSource Self}}"/> <Setter Property="Content"> <Setter.Value> <TextBlock Text="{Binding WidgetName, RelativeSource={RelativeSource AncestorType=local:Widgetvm}}"/> </Setter.Value> </Setter> </Style> </ItemsControl.ItemContainerStyle> <!-- 可以删掉原来的ItemTemplate定义 -->
更符合MVVM规范的可选优化
让ViewModel继承UI控件违反了MVVM的分层原则,后续维护成本更高。你可以保留WidgetVM为纯CLR对象,通过附加属性或XAML行为给Thumb控件实现拖拽功能,完全不会触发绑定异常,也能正常使用DragDelta事件。
内容的提问来源于stack exchange,提问作者ChrisOBrien
相关产品推荐
相关产品推荐

