简体   繁体   English

如何在treeview wpf MVVM中分组复选框。

[英]How to group checkboxes in treeview wpf mvvm when selection range is [0,1]

I have made a tree View in wpf Using MVVM.我使用 MVVM 在 wpf 中创建了一个树视图。

it is working fine but here is one problem that leaf node contains some checkboxes and user have only two options either to select one or none.它工作正常,但这是一个问题,叶节点包含一些复选框,用户只有两个选项 select 一个或没有。

树视图示例图像

So here how i can restricted user to select maximum only one cold drink.所以在这里我如何将用户限制为 select 最多只能喝一种冷饮。

I did one trick but it didn't work that when i have already selected a drink and then i select another one than i set the last selected value in the observable collection to false but it doesn't affect on view and selected check boxes remains selected although in collection only one option's value is true.我做了一个技巧,但是当我已经选择了一种饮料然后我 select 另一个比我将可观察集合中的最后一个选定值设置为 false 但它不影响视图并且选中的复选框仍然存在时,它不起作用选择虽然在集合中只有一个选项的值为真。

I cant use radio button instedof checkbox becasue user can select none of the options and i cant give an additional option for none of the above.我不能使用单选按钮 instedof 复选框,因为用户可以 select 没有任何选项,并且我无法为上述任何选项提供额外的选项。

If any one have any solution so please let me know I'll be very thankful.如果有人有任何解决方案,请告诉我,我将非常感激。

updated question: i think i didn't define my problem in a proper way so i am giving my code snipperts here hope by this i'll get the solution of my problem...更新的问题:我想我没有以正确的方式定义我的问题,所以我在这里给我的代码截图希望我能得到我的问题的解决方案......

My View Model Class我的观点 Model Class

namespace TestViewModels
{
 public class ViewModel :ViewModelBase

{

    private ObservableCollection<AvailableProducts> _MyTreeViewProperty

    public ObservableCollection<AvailableProducts> MyTreeViewProperty
    {
        get { return _MyTreeViewProperty
        set { _MyTreeViewProperty value; 
        RaisePropertyChanged("MyTreeViewProperty");}
    }

} }

public class AvailableProducts

{

     private string _BrandName;

    public string BrandName
    {
        get { return _BrandName
        set { _BrandName = value; }
    }


    private bool _IsExpanded;
    public bool IsExpanded
    {
        get
        {
            return _IsExpanded;

        }
        set
        {
            _IsExpanded = value;

        }
    }

    private ObservableCollection<ProductTypes> _MyProductTypes

    public ObservableCollection<ProductTypes> MyProductTypes
    {
        get { return _MyProductTypes}
        set { _MyProductTypes= value; }
    }


}

public class ProductTypes
{
    private string _ProductTypeName;

    public string ProductTypeName
    {
        get { return _ProductTypeName;
        set { _ProductTypeNamevalue; }
    }



    private ObservableCollection<ProductSubTypes> _ProdSubTypes;

    public ObservableCollection<ProductSubTypes> ProdSubTypes
    {
        get { return _ProdSubTypes;}
        set { _ProdSubTypes;= value; }
    }


}


public class ProductSubTypes
{
    private string _ProductSubTypeName;

    public string ProductSubTypeName
    {
        get { return _ProductSubTypeName;
        set { _ProductSubTypeName;}
    }

    private int _ParentID;

    public int ParentID
    {
        get { return _ParentID;}
        set { _ParentID;= value; }
    }


    private bool _IsAssigned;

    public bool IsAssigned
    {
        get { return _IsAssigned; }
        set
        {
            _IsAssigned = value;

            if _ParentID;!= 0)
            {
               //updating data in database
               //Calling and setting new collection value in property

              //issue : updated collection sets in setter of MyTreeViewProperty but before calling getter
            // it comes to IsAssigned getter so view doesnt get updated  collection of MyTreeViewProperty 

            }
            RaisePropertyChanged("IsAssigned");
        }
    }


}

} }

View看法

<Page x:Class="ShiftManagerViews.Pages.ProductTreeSelection
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
  DataContext="{Binding ProductsTree, Source={StaticResource Locator}}"
  mc:Ignorable="d"  Width="870" Height="665"

>

     <TreeView  Margin="10,10,0,13" ItemsSource="{Binding MyTreeViewProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"  HorizontalAlignment="Left"

VerticalAlignment="Top" Width="800" Height="Auto" MinHeight="400" MaxHeight="800">
                <TreeView.ItemContainerStyle>

                    <Style TargetType="{x:Type TreeViewItem}">
                        <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
                     </Style>
                </TreeView.ItemContainerStyle>

                <TreeView.Resources>

                        <HierarchicalDataTemplate DataType="{x:Type local:AvailableProducts}"
                                      ItemsSource="{Binding MyProductTypes}"> 
                        <WrapPanel>
                            <Image Width="20" Height="20" Source="/ShiftManagerViews;component/Images/12.bmp"/>
                            <Label Content="{Binding BrandName}" FontSize="14"/>
                        </WrapPanel>
                    </HierarchicalDataTemplate>

                        <HierarchicalDataTemplate DataType="{x:Type local:ProductTypes}"
                                      ItemsSource="{Binding ProdSubTypes}">
                        <WrapPanel>
                            <Image Width="18" Height="15" Source="/ShiftManagerViews;component/Images/12.bmp"/>
                            <Label Content="{Binding ProductTypeName}" FontSize="13"/>
                        </WrapPanel>
                    </HierarchicalDataTemplate>
                        <!-- the template for showing the Leaf node's properties-->
                        <DataTemplate DataType="{x:Type local:ProductSubTypes}">
                            <StackPanel>

                                <CheckBox IsChecked="{Binding IsAssigned, Mode=TwoWay}" Content="{Binding ProductSubTypeName}" Height="25">

                            </CheckBox>
                            </StackPanel>
                        </DataTemplate>
                    </TreeView.Resources>
                </TreeView>

What about using a ListBox to display sub-items instead of a TreeView ?使用ListBox而不是TreeView来显示子项怎么样? You can style that so the items contain a CheckBox to show IsSelected instead of highlighting the item.您可以设置样式,以便项目包含CheckBox以显示IsSelected而不是突出显示项目。

I'd suggest your user interface is wrong.我建议您的用户界面是错误的。 If the user can only pick one then it would be better to swap these for radio buttons and add a "None of the above" option.如果用户只能选择一个,那么最好将这些替换为单选按钮并添加“以上都不是”选项。 That'll then give you the behaviour you want for free and your UI will be more intuitive.然后,这将为您免费提供您想要的行为,并且您的 UI 将更加直观。

EDIT: Since you say you can't add a "None" option and want to use a checkbox (even though I strongly disagree on checkboxes where a radio button is more appropriate - a common UI error)...编辑:既然你说你不能添加一个“无”选项并想使用一个复选框(即使我强烈不同意单选按钮更合适的复选框 - 一个常见的 UI 错误)......

The technical problem you are probably facing is that an ObservableCollection only raises notification events if the collection itself changes.您可能面临的技术问题是 ObservableCollection 仅在集合本身发生更改时才引发通知事件。 ie Only if items are added or removed.即仅当添加或删除项目时。 It does not raised events when items within the collection change, therefore the changing the status of the checkbox in the code will not raise the event for the UI binding to act on.当集合中的项目发生更改时,它不会引发事件,因此更改代码中复选框的状态不会引发 UI 绑定操作的事件。

One solution to this to write a custom class that extends ObservableCollection that does provide this behaviour一种解决方案是编写自定义 class 扩展 ObservableCollection 提供此行为

From MSDN :来自MSDN

If you need to know if someone has changed a property of one of the items within the collection, you'll need to ensure that the items in the collection implement the INotifyPropertyChanged interface, and you'll need to manually attach property changed event handlers for those objects.如果您需要知道是否有人更改了集合中某个项目的属性,则需要确保集合中的项目实现 INotifyPropertyChanged 接口,并且您需要手动附加属性更改事件处理程序那些物体。 No matter how you change properties of objects within the collection, the collection's PropertyChanged event will not fire.无论您如何更改集合中对象的属性,集合的 PropertyChanged 事件都不会触发。 As a matter of fact, the ObservableCollection's PropertyChanged event handler is protected—you can't even react to it unless you inherit from the class and expose it yourself.事实上,ObservableCollection 的 PropertyChanged 事件处理程序是受保护的——除非您从 class 继承并自己公开它,否则您甚至无法对它做出反应。 You could, of course, handle the PropertyChanged event for each item within the collection from your inherited collection当然,您可以从继承的集合中为集合中的每个项目处理 PropertyChanged 事件

I upvoted Rachel's answer, it is a common way in WPF to databind sets of radio buttons or check boxes.我赞成 Rachel 的回答,这是 WPF 中的一种常见方式,用于数据绑定单选按钮或复选框集。 If you still want to go the tree view way, below code works.如果您仍然想要 go 树视图方式,下面的代码有效。 All view related code is in the view, so below code follows MVVM principles.所有视图相关的代码都在视图中,所以下面的代码遵循 MVVM 原则。 If you are a MVVM purist you can put the code behind and a TreeView control in a user control if you do not want any code behind.如果您是 MVVM 纯粹主义者,您可以将代码放在后面,如果您不希望后面有任何代码,则可以将 TreeView 控件放在用户控件中。

XAML: XAML:

<TreeView ItemsSource="{Binding Path=Drinks}">
    <TreeView.ItemTemplate>
        <DataTemplate>
            <CheckBox Content="{Binding .}" Checked="OnCheckBoxChecked" Unchecked="OnCheckBoxUnchecked" Loaded="OnCheckBoxLoaded" />
        </DataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

Code behind + VM:代码背后+ VM:

public partial class Window1
{
    public Window1()
    {
        InitializeComponent();

        DataContext = new VM();
    }

    private void OnCheckBoxChecked(object sender, System.Windows.RoutedEventArgs e)
    {
        foreach (CheckBox checkBox in _checkBoxes.Where(cb => cb != sender))
        {
            checkBox.IsChecked = false;
        }

        (DataContext as VM).CurrentDrink = (sender as CheckBox).Content.ToString();
    }

    private void OnCheckBoxUnchecked(object sender, System.Windows.RoutedEventArgs e)
    {
        (DataContext as VM).CurrentDrink = null;
    }

    private void OnCheckBoxLoaded(object sender, System.Windows.RoutedEventArgs e)
    {
        _checkBoxes.Add(sender as CheckBox);
    }

    private List<CheckBox> _checkBoxes = new List<CheckBox>();
}

public class VM
{
    public List<string> Drinks
    {
        get
        {
            return new List<string>() { "Coffee", "Tea", "Juice" };
        }
    }

    public string CurrentDrink { get; set; }
}

I did one trick but it didn't work that when i have already selected a drink and then i select another one than i set the last selected value in the observable collection to false but it doesn't affect on view and selected check boxes remains selected although in collection only one option's value is true.我做了一个技巧,但是当我已经选择了一种饮料然后我 select 另一个比我将可观察集合中的最后一个选定值设置为 false 但它不影响视图并且选中的复选框仍然存在时,它不起作用选择虽然在集合中只有一个选项的值为真。

Make sure that your child objects (AvailableProducts and SubProductTypes) also implement INotifyPropertyChanged, this will make sure that the UI receives changes when modify the object.确保您的子对象(AvailableProducts 和 SubProductTypes)也实现 INotifyPropertyChanged,这将确保 UI 在修改 object 时收到更改。

Once all of you objects update the UI properly you will be able to layer in, and test, whatever custom business logic you need.一旦所有对象都正确更新了 UI,您将能够分层并测试您需要的任何自定义业务逻辑。

So if you have a product type that can only have one sub chosen, you could add a property on ProductType called OnlyAllowOneChild.因此,如果您的产品类型只能选择一个子,您可以在 ProductType 上添加一个名为 OnlyAllowOneChild 的属性。 Whenever, a child object raises a IsAssigned changed event, the parent can set false all other children.每当子 object 引发 IsAssigned 更改事件时,父级可以将所有其他子级设置为 false。 This of course requires you to have the parent either register for the children's PropertyChangedEvent, or got grab an EventAggregator (MVVMLight Messenger, or PRISM EvenAggregator) and create a messaging system.这当然需要您让父母为孩子的 PropertyChangedEvent 注册,或者获得一个 EventAggregator(MVVMLight Messenger,或 PRISM EvenAggregator)并创建一个消息传递系统。

Finally i am succeeded to solve my problem.最后我成功地解决了我的问题。

on Is Assigned property i am updating my database values and calling a method in view using MVVM Light messaging and passing currently selected leaf's parent id in it as a parameter...在 Is Assigned 属性上,我正在更新我的数据库值并使用 MVVM Light 消息传递视图中的方法,并将当前选定的叶子的父 ID 作为参数传递给它......

Added a property in class Product Types to expand the parent node of the last selected leaf..在 class 产品类型中添加了一个属性以展开最后选择的叶子的父节点。

In view's method i am refreshing data context's source and passing currently selected leaf's parent id tO the VM to set its Is Expanded property value to true...在视图的方法中,我正在刷新数据上下文的源并将当前选择的叶子的父 ID 传递给 VM 以将其 Is Expanded 属性值设置为 true ...

By this my view is working perfectly as same as i want... If any body have solution better than this than I'll be happy to know.通过这一点,我的观点与我想要的完全一样......如果任何机构有比这更好的解决方案,我会很高兴知道。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM