当前位置:  开发笔记 > 编程语言 > 正文

在wpf中显示所选列表框项的数据

如何解决《在wpf中显示所选列表框项的数据》经验,为你挑选了1个好方法。

我正在寻求一些帮助.我已经创建了一个非常基本的MVVM设置.我的对象叫做VNode,它有Name,Age,Kids属性.我想要发生的是当用户选择左侧的VNode时,它会在右侧显示更多深度数据作为下图中的场景.我不知道该怎么做.

图1:电流

在此输入图像描述

图2:目标

在此输入图像描述

如果您不想使用下面的代码重新创建窗口,可以从此处获取项目解决方案文件:DropboxFiles

VNode.cs

namespace WpfApplication1
{
    public class VNode
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public int Kids { get; set; }
    }
}

MainWindow.xaml



    
        
    
    
        
            
            
            
        

        
            
                
                    
                        
                        
                    
                
            
        

        

        
            
                
                    
                        
                        
                        
                        
                        
                        
                    
                
            
        

    

MainViewModel.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApplication1
{
    public class MainViewModel : ObservableObject
    {
        private ObservableCollection _vnodes;
        public ObservableCollection VNodes
        {
            get { return _vnodes; }
            set
            {
                _vnodes = value;
                NotifyPropertyChanged("VNodes");
            }
        }

        Random r = new Random();

        public MainViewModel()
        {
            //hard coded data for testing
            VNodes = new ObservableCollection();
            List names = new List() { "Tammy", "Doug", "Jeff", "Greg", "Kris", "Mike", "Joey", "Leslie", "Emily","Tom" };
            List ages = new List() { 32, 24, 42, 57, 17, 73, 12, 8, 29, 31 };

            for (int i = 0; i < 10; i++)
            {
                VNode item = new VNode();

                int x = r.Next(0,9);
                item.Name = names[x];
                item.Age = ages[x];
                item.Kids = r.Next(1, 5);
                VNodes.Add(item);
            }
        }
    }
}

ObservableObject.cs

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace WpfApplication1
{
    public class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

更新 为了举例,如何演示用户是否只选择右侧列表框中的单个项目,然后在右侧显示所选项目更深入的数据,如下图所示?

在此输入图像描述



1> 小智..:

这里有三个半答案.第一个是良好的通用WPF实践,在ListBox的特定情况下不起作用.第二个是针对ListBox问题的快速而肮脏的解决方法,最后一个是最好的,因为它在后面的代码中什么都不做.最少的代码背后是最好的代码.

第一种方法不需要您在ListBox中显示的任何项目.它们可以是字符串或整数.如果你的项目类型(或类型)是一个类(或类),并且有更多的肉,并且你想让每个实例都知道它是否被选中,我们接下来就会知道.

你需要给你的另一视图模型ObservableCollection称为SelectedVNodes或一些这样的.

    private ObservableCollection _selectedvnodes;
    public ObservableCollection SelectedVNodes
    {
        get { return _selectedvnodes; }
        set
        {
            _selectedvnodes = value;
            NotifyPropertyChanged("SelectedVNodes");
        }
    }

    public MainViewModel()
    {
        VNodes = new ObservableCollection();
        SelectedVNodes = new ObservableCollection();

        // ...etc., just as you have it now.

If System.Windows.Controls.ListBox weren't broken, then in your first ListBox, you would bind SelectedItems to that viewmodel property:


And the control would be in charge of the content of SelectedVNodes. You could also change SelectedVNodes programmatically, and that would update both lists.

But System.Windows.Controls.ListBox is broken, and you can't bind anything to SelectedItems. The simplest workaround is to handle the ListBox's SelectionChanged event and kludge it in the code behind:

XAML:


C#:

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    ListBox lb = sender as ListBox;
    MainViewModel vm = DataContext as MainViewModel;
    vm.SelectedVNodes.Clear();
    foreach (VNode item in lb.SelectedItems)
    {
        vm.SelectedVNodes.Add(item);
    }
}

Then bind ItemsSource in your second ListBox to SelectedVNodes:


And that should do what you want. If you want to be able to update SelectedVNodes programmatically and have the changes reflected in both lists, you'll have to have your codebehind class handle the PropertyChanged event on the viewmodel (set that up in the codebehind's DataContextChanged event), and the CollectionChanged event on viewmodel.SelectedVNodes -- and remember to set the CollectionChanged handler all over again every time SelectedVNodes changes its own value. It gets ugly.

A better long-term solution would be to write an attachment property for ListBox that replaces SelectedItems and works right. But this kludge will at least get you moving for the time being.

Update

OP提出了第二种方法.我们不是维护选定的项目集合,而是在每个项目上放置一个标志,并且viewmodel具有主项目列表的过滤版本,仅返回所选项目.我在如何将VNode.IsSelected绑定到ListBoxItem上的IsSelected属性上绘制了一个空白,所以我只是在后面的代码中执行了此操作.

VNode.cs:

using System;
namespace WpfApplication1
{
    public class VNode
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public int Kids { get; set; }

        //  A more beautiful way to do this would be to write an IVNodeParent
        //  interface with a single method that its children would call 
        //  when their IsSelected property changed -- thus parents would 
        //  implement that, and they could name their "selected children" 
        //  collection properties anything they like. 
        public ObservableObject Parent { get; set; }

        private bool _isSelected = false;
        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                if (value != _isSelected)
                {
                    _isSelected = value;
                    if (null == Parent)
                    {
                        throw new NullReferenceException("VNode.Parent must not be null");
                    }
                    Parent.NotifyPropertyChanged("SelectedVNodes");
                }
            }
        }
    }
}

MainViewModel.cs:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApplication1
{
    public class MainViewModel : ObservableObject
    {
        private ObservableCollection _vnodes;
        public ObservableCollection VNodes
        {
            get { return _vnodes; }
            set
            {
                _vnodes = value;
                NotifyPropertyChanged("VNodes");
                NotifyPropertyChanged("SelectedVNodes");
            }
        }

        public IEnumerable SelectedVNodes
        {
            get { return _vnodes.Where(vn => vn.IsSelected); }
        }

        Random r = new Random();

        public MainViewModel()
        {
            //hard coded data for testing
            VNodes = new ObservableCollection();

            List names = new List() { "Tammy", "Doug", "Jeff", "Greg", "Kris", "Mike", "Joey", "Leslie", "Emily","Tom" };
            List ages = new List() { 32, 24, 42, 57, 17, 73, 12, 8, 29, 31 };

            for (int i = 0; i < 10; i++)
            {
                VNode item = new VNode();

                int x = r.Next(0,9);
                item.Name = names[x];
                item.Age = ages[x];
                item.Kids = r.Next(1, 5);
                item.Parent = this;
                VNodes.Add(item);
            }
        }
    }
}

MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication1
{
    /// 
    /// Interaction logic for MainWindow.xaml
    /// 
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            foreach (VNode item in e.RemovedItems)
            {
                item.IsSelected = false;
            }
            foreach (VNode item in e.AddedItems)
            {
                item.IsSelected = true;
            }
        }
    }
}

MainWindow.xaml(部分):

    
        
            
                
                    
                    
                
            
        
    

    

    
        
            
                
                    
                    
                    
                    
                    
                    
                
            
        
    
更新2

最后,这里是你如何使用绑定(感谢OP为我弄清楚如何将数据项属性绑定到ListBoxItem属性 - 我应该能够接受他的评论作为答案!):

在MainWindow.xaml中,删除SelectionCanged事件(yay!),并设置Style以仅对第一个ListBox中的项进行绑定.在第二个ListBox中,该绑定将产生问题,我将留给其他人解决; 我猜想通过摆弄通知和作业的顺序VNode.IsSelected.set可以解决这个问题,但我可能会非常错误.无论如何,绑定在第二个ListBox中没有用处,所以没有理由在那里有它.

    
        
            
        
        
            
                
                    
                    
                
            
        
    

...我从代码隐藏中删除了事件处理程序方法.但你根本没有添加它,因为你比我更聪明,你开始使用最后一个版本的答案.

In VNode.cs, VNode becomes an ObservableObject so he can advertise his selection status, and he also fires the appropriate notification in IsSelected.set. He still has to fire the change notification for his Parent's SelectedVNodes property, because the second listbox (or any other consumer of SelectedVNodes) needs to know that the set of selected VNodes has changed.

Another way to do that would be to make SelectedVNodes an ObservableCollection again, and have VNode add/remove himself from it when his selected status changes. Then the viewmodel would have to handle CollectionChanged events on that collection, and update the VNode IsSelected properties when they're added to it or removed from it. If you do that, it's very important to keep the if in VNode.IsSelected.set, to prevent infinite recursion.

using System;
namespace WpfApplication1
{
    public class VNode : ObservableObject
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public int Kids { get; set; }

        public ObservableObject Parent { get; set; }

        private bool _isSelected = false;
        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                if (value != _isSelected)
                {
                    _isSelected = value;
                    if (null == Parent)
                    {
                        throw new NullReferenceException("VNode.Parent must not be null");
                    }
                    Parent.NotifyPropertyChanged("SelectedVNodes");
                    NotifyPropertyChanged("IsSelected");
                }
            }
        }
    }
}
Update 3

OP asks about displaying a single selection in a detail pane. I left the old multi-detail pane in place to demonstrate sharing a template.

Version 3

That's pretty simple to do, so I elaborated a bit. You could do this only in the XAML, but I threw in a SelectedVNode property in the viewmodel to demonstrate that as well. It's not used for anything, but if you wanted to throw in a command that operated on the selected item (for example), that's how the view model would know which item the user means.

MainViewModel.cs

//  Add to MainViewModle class
private VNode _selectedVNode = null;
public VNode SelectedVNode
{
    get { return _selectedVNode; }
    set
    {
        if (value != _selectedVNode)
        {
            _selectedVNode = value;
            NotifyPropertyChanged("SelectedVNode");
        }
    }
}

MainWindow.xaml:



    
        

        
            
                
                    
                        
                    
                    
                        
                            
                            
                            
                        
                        
                            
                            
                            
                            
                        

                        
                            
                                
                                    
                                
                                
                                
                            
                        

                        
                        
                        
                        Age: 
                        Kids: 
                    
                
            
            
                
                    
                
            
        

        
    

    
        
    

    
        
            
            
            
        
        
            
            
        

        
            
                
            
            
                
                    
                        
                        
                    
                
            
        

        

        
            
        

        

    

推荐阅读
echo7111436
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有