zl程序教程

您现在的位置是:首页 >  后端

当前栏目

【wpf】ItemsControl 的Binding 小技巧

WPF 技巧 Binding
2023-09-11 14:14:50 时间

 ItemsControl 非常常用和好用的控件,我经常将之用于配置界面!

比如这么一个配置界面:

 整体是一个ItemsControl,每个子界面就是其中的一个Item。

ItemsControl 的 ItemsSource 绑定到 ParameterInfo 的集合

public ObservableCollection<ParameterInfo> configInfos { get; set; } = new ObservableCollection<ParameterInfo>();

每个Item 绑定的内容都对应集合元素的项 ParameterInfo

public class ParameterInfo : BindableBase
    {
        public const string file_name = "调试参数";
        #region 模板匹配相关参数
        public MatchInfo matchInfo { get; set; } = new MatchInfo();
        private string mdPath;
        /// <summary>
        /// 模板路径
        /// </summary>
        public string MdPath
        {
            get { return mdPath; }
            set { SetProperty(ref mdPath, value); }
        }
        #endregion
        #region 瑕疵检测
        public DefectInfo defectInfo { get; set; } = new DefectInfo();
        #endregion
        private string proName = "未命名";
        /// <summary>
        /// 项目名称
        /// </summary>
        public string ProName
        {
            get { return proName; }
            set { SetProperty(ref proName, value); }
        }
    }

这里有个问题,每个子界面都有一个“打开” 按键。而 打开按键的命令绑定不在ParameterInfo 中,而是在ViewModel中,如果直接填写 命令名称,就会绑定失败。

于是就有写法:

<Button MinWidth="50" DockPanel.Dock="Right" 
 Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.OpenMdCmd}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}}">打开</Button>

首先我们看到:

Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.OpenMdCmd}"

就是解决在ItemsControl,无法找到数据源的问题,使用了 RelativeSource,通过 FindAncestor的方式,找到了 UserControl 的 DataContext 中的 命令属性定义。

我在之前的文章中也介绍到了:

【wpf】深度解析,Bingding是如何寻找数据源的_wpf findan_code bean的博客-CSDN博客目前我常用存放数据源的一般控件都有Resources和DataContext属性,列表控件会多一个ItemsSource。可以放多个资源,但是需要给每一个资源指定一个key。而和只能放一个对象(不需要指定key)https://blog.csdn.net/songhuangong123/article/details/126195727?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167765404916800213065489%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=167765404916800213065489&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-126195727-null-null.article_score_rank_blog&utm_term=TemplatedParent&spm=1018.2226.3001.4450#:~:text=%E3%80%90wpf%E3%80%91%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90%EF%BC%8CBingding%E6%98%AF%E5%A6%82%E4%BD%95%E5%AF%BB%E6%89%BE%E6%95%B0%E6%8D%AE%E6%BA%90%E7%9A%84可以具体看看

此时,OpenMdCmd确实已经绑定成功,但是有个问题,每个子界面的 “打开按钮” 都会调用这个命令,OpenMdCmd对应的方法,如何得知具体是哪个子界面的调用的呢

这里,就进入今天的主题内容,

CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}}"

我们这里的 CommandParameter,就是给CMD传递参数,而且也使用了 RelativeSource 的方式。

这里的 Mode 使用的 TemplatedParent。这种方式,之前的那篇文章也提到过,不过当时只知道会用于 “控件模板”, 想不到在这里,用到“数据模板” 也有它的作用。

于是  OpenMdCmd 里 就接收一下这个参数,看看是个啥?

名称类型
item{"子母勾"}object {System.Windows.Controls.ContentPresenter}
◢ Content"子母勾"object {VisualMatching.Models.ParameterInfo}
proName"子母勾"string
MdPathnullstring
ProName"子母勾"string
▶ PropertyChanged{Method = System.ComponentModel.PropertyChangedEventHandler
▶ defectInfoVisualMatching.Models.DefectInfo
▶ matchInfoVisualMatching.Models.MatchInfo
mdPathnullstring
▶ 静态成员

就是 System.Windows.Controls.ContentPresenter,这个就是数据模板对应的玩意儿。

关于 ContentPresenter 我的另一篇文章有详细讲解:

【wpf】子项容器模板 控件模板 数据模板 逻辑树 视觉树 之间的关系_wpf模板 树_code bean的博客-CSDN博客wpf中的那些模板之深度解析https://blog.csdn.net/songhuangong123/article/details/126071796#:~:text=%E3%80%90wpf%E3%80%91%E5%AD%90%E9%A1%B9%E5%AE%B9%E5%99%A8%E6%A8%A1%E6%9D%BF%20%E6%8E%A7%E4%BB%B6%E6%A8%A1%E6%9D%BF%20%E6%95%B0%E6%8D%AE%E6%A8%A1%E6%9D%BF%20%E9%80%BB%E8%BE%91%E6%A0%91%20%E8%A7%86%E8%A7%89%E6%A0%91%20%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB

继续往里面看,它其中的一个属性,Content,就是我们自定义的子项类 ParameterInfo

也就是说Content就是你当前选择的子项Item对应的数据。那么在进入主题时,提出问题也就迎刃而解了!

最近,再通过 Path 属性 过滤一下,就能直接拿到  ParameterInfo 对象:

CommandParameter="{Binding  Path=Content, RelativeSource={RelativeSource Mode=TemplatedParent}}"

小结:

1 xaml写法:

<Button Content="+" 
Command="{Binding RelativeSource={RelativeSource 
Mode=FindAncestor, AncestorType={x:Type UserControl}},
Path=DataContext.AddSelectValueCmd}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}}"/>

2 后台写法:(这里参数直接用ContentPresenter类型)

 AddSelectValueCmd = new DelegateCommand<ContentPresenter>((obj) => {
       var a = obj.Content;
 });

注意这里的obj是ContentPresenter类型,而obj.Content对应就是binding数据的内容!

如果后台想直接得到Content,我们就可以在前台中添加 Path=Content 进行过滤:

CommandParameter="{Binding  Path=Content, RelativeSource={RelativeSource Mode=TemplatedParent}}"

3 最优写法

最后我觉得最优的写法就是:

<Button Content="+" 
Command="{Binding RelativeSource={RelativeSource 
Mode=FindAncestor, AncestorType={x:Type UserControl}},
Path=DataContext.AddSelectValueCmd}"
CommandParameter="{Binding Path=Content, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>

不过此时 DelegateCommand  的泛型类型就不是 ContentPresenter,而是实际 Content 对应的类型!