Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 595|回复: 0

【WPF】CommandParameter解决多传参问题

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-7-9 19:48:42 | 显示全部楼层 |阅读模式

    方法一:传参按钮控件自身绑定的ItemSource

    用WAF框架实现MVVM,按钮的点击事件都要通过Command来传递到这个View对应的ViewModel上,再通过ViewModel传递到上层的Controller层,在Controller层通过DelegateCommand处理按钮真正的事件。有时候需要给该Command附加上一些参数(CommandParameter),但是默认CommandParameter只能传递一个参数。谷歌搜到的解决方法很复杂,于是想了个办法CommandParameter参数传递的是这个按钮控件自身绑定的ItemSource,然后通过从ItemSource身上的DataContext来拿到数据,再截取字符串分割得到想要的部分数据(或者强转为ItemSource对应的实体类)。

    正常情况下,Button的绑定:

    <Button Command="{Binding BtnCommand}" />

    这个Command会沿着View –> ViewModle –> Controller层传递。

    如果这个Button是ListBox的Item,这个ListBox的Item要使用数据模板,且ItemsSource绑定到了这组Button的数据源,是这样绑:

    <ListBox x:Name="listBox" ItemsSource="{Binding DataList}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid x:Name="grid">
                    <local:WaitingProgress/>
                    <Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.BtnCommand}" CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}}"> <!-- 传参是Button控件绑定的ItemsSource,即这里是DataList列表 -->
                        <Image Tag="{Binding id}" x:Name="img" Stretch="UniformToFill" Width="150" Height="150" webImg:ImageDecoder.Source="{Binding icon}">
                        </Image>
                    </Button>
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel Name="wrapPanel" HorizontalAlignment="Stretch" />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>

    这个ItemSource绑定的DataList是ViewModle中的一个实例列表。ViewModel关键代码如下:

    private ICommand refreshDesignCommand; // 向上传递这个Command:View-->ViewModel-->Controller
    public ICommand RefreshDesignCommand
    {
        get { return refreshDesignCommand; }
        set { SetProperty(ref refreshDesignCommand, value); }
    }
    
    private ObservableCollection<GoodsJsonData> dataList = null;
    public ObservableCollection<GoodsJsonData> DataList
    {
        get { return dataList; }
        set { dataList = value; }
    }

    实体类:

    public class GoodsJsonData
    {
        public string id { get; set; }      // 还可用于图片被点击调时,标记出是哪个缩略图被点击
        public string icon { get; set; }    // 缩略图
        public string image { get; set; }   // 大图
        public string model { get; set; }   // 该商品对应的模型XML
    
        public override string ToString()
        {
            return "id = " + id + " , icon = " + icon + " , image = " + image + " , model = " + model;
        }
    }

    Controller层的关键代码:

    private readonly DelegateCommand refreshDesignCommand;  // 缩略图的点击回调
    
    [ImportingConstructor]
    public WebImageController()
    {
        this.refreshDesignCommand = new DelegateCommand(p => RefreshDesignCommand((Button)p));
    }
    
    private void RefreshDesignCommand(Button btn)
    {
        // 方法一:将DataContext打印字符串,截取出目标数据
        string dataContext = btn.DataContext.ToString();
        System.Console.WriteLine(dataContext);          // id = 000201 , icon = http://192.168.1.222/mjl/4-01.png , image = 2/造型/4-01.png , model = xml/qiang07.xml
        // 截取字符串来获得目标数据。
    
        // 方法二:将DataContext强转为ItemSource对应的实体类类型
        GoodsJsonData data = (GoodsJsonData)btn.DataContext;
        // do what you want !

    坑点:

    • 如果这个DataList列表的内容需要同步刷新,则类型**必须是**ObservableCollection。否则就算控件与数据绑定成功,控件只在初始化时能够正确显示数据,之后数据发生改变时,控件不会自动刷新。
    • WPF可以传递控件自身绑定的ItemSource数据,通过ItemSource携带的DataContext内容,来代替CommandParameter多传参的蛋疼问题。

    其他建议:

    • 想要在一个控件上传递多个参数,可以传递控件自身,用控件的Tag和Uid属性绑定上数据。

     

    今天在StackOverflow看到一个关于解决Command和CommandParameter的工具:
    http://xcommand.codeplex.com/
    以后可能会用到,先Mark。之后抽空看看。


     

    方法二:多路绑定MultiBinding结合转换器Converter的使用

    该方法是网上搜到的主流方式。

     


     

    方法三:其他Trick

    思路:用其他控件的属性来记录数据。传参时传递按钮控件自身,再通过按钮控件的视觉树布局找到找到绑定了其他数据的控件。

    XAML:

    <Grid>
        <Button Content="测试按钮"
            Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.YourCommand}"
            CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}}">
        </Button>
        <!-- 用于给点击按钮时,传递多个参数 -->
        <Grid x:Name="studentIdGrid" Tag="{Binding studentId}"/>
        <Grid x:Name="studentNameGrid" Tag="{Binding studentName}"/>
        <Grid x:Name="studentAgeGrid" Tag="{Binding studentAge}"/>
    </Grid>

    Controller:

    // 按钮点击触发的事件
    private void YourCommand(object p)
    {
        Button btn = (Button)p; // 传参传递的是控件自身
        DependencyObject parent = VisualTreeHelper.GetParent(btn);
        List<Grid> list = this.FindVisualChildren<Grid>(parent);
        string studentId = list[0].Tag.ToString();
        string studentName = (int)(list[1].Tag);
        int studentAge = list[2].Tag.ToString();
        
        // do something...
    }
    
    
    // 从视觉树找到目标控件的所有子控件
    private List<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
    {
        List<T> list = new List<T>();
        if (depObj != null)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                if (child != null && child is T)
                {
                    list.Add((T)child);
                }
    
                List<T> childItems = FindVisualChildren<T>(child); // 递归
                if (childItems != null && childItems.Count() > 0)
                {
                    foreach (var item in childItems)
                    {
                        list.Add(item);
                    }
                }
            }
        }
        return list;
    }

    小结:

    • 按钮的CommandParameter绑定了自身,将自身作为点击后触发的回调函数的参数传入。再用该按钮控件去找到其他控件或UI元素。
    • 使用了按钮的兄弟节点Grid的Tag属性来绑定目标数据,选择用Grid是因为我们只想用它来传参,而不需要看到它。因此用其他UI元素的Tag属性来传参,再设置Visibility="Collapse"也是可行的。
    • 同样选择用兄弟节点也不是必须的。也可以是父节点或子节点,只要能通过视觉树VisualTreeHelper找到就行。

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-10 20:57 , Processed in 0.061542 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表