当选择范围为 [0,1] 时,如何对树视图 wpf mvvm 中的复选框进行分组

发布于 2024-11-29 01:13:21 字数 5662 浏览 0 评论 0原文

我使用 MVVM 在 wpf 中制作了一个树视图。

它工作正常,但有一个问题,叶节点包含一些复选框,并且用户只有两个选项,要么选择一个,要么不选择。

Tree View Sample Image

那么这里我如何限制用户最多只能选择一种冷饮。

我做了一个技巧,但它不起作用,当我已经选择了一种饮料,然后选择另一种饮料时,我将可观察集合中的最后一个选定值设置为 false,但它不会影响视图,并且选定的复选框仍然存在尽管集合中只有一个选项的值是 true,但已选择。

我无法使用单选按钮插入复选框,因为用户无法选择任何选项,并且我无法为上述任何选项提供附加选项。

如果有人有任何解决方案,请告诉我,我将非常感激。

更新的问题: 我认为我没有以正确的方式定义我的问题,所以我在这里给出我的代码片段,希望通过这个我能得到我的问题的解决方案...

我的视图模型类

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");
        }
    }


}

}

<强>查看

<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>

I have made a tree View in wpf Using MVVM .

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 .

Tree View Sample Image

So here how i can restricted user to select maximum only one cold drink.

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.

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.

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 o f my problem...

My View 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>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

悍妇囚夫 2024-12-06 01:13:21

使用 ListBox 代替 TreeView 显示子项怎么样?您可以设置样式,以便项目包含 CheckBox 以显示 IsSelected 而不是突出显示该项目。

What about using a ListBox to display sub-items instead of a TreeView? You can style that so the items contain a CheckBox to show IsSelected instead of highlighting the item.

凉月流沐 2024-12-06 01:13:21

我建议你的用户界面是错误的。如果用户只能选择一个,那么最好将它们替换为单选按钮并添加“以上都不是”选项。然后,这将免费为您提供您想要的行为,并且您的用户界面将更加直观。

编辑:既然你说你不能添加“无”选项并且想要使用复选框(尽管我强烈不同意单选按钮更合适的复选框 - 常见的 UI 错误)。 ..

您可能面临的技术问题是 ObservableCollection 仅在集合本身发生更改时才会引发通知事件。即仅当添加或删除项目时。当集合中的项目发生更改时,它不会引发事件,因此更改代码中复选框的状态不会引发 UI 绑定操作的事件。

解决此问题的一种方法是编写一个扩展 ObservableCollection 的自定义类,该类确实提供此行为

来自 MSDN

如果您需要知道某人是否更改了其中一个的属性
集合中的项目,您需要确保项目
该集合实现 INotifyPropertyChanged 接口,并且
您需要手动附加属性更改事件处理程序
那些物体。无论你如何改变内部对象的属性
集合,集合的 PropertyChanged 事件将不会触发。
事实上,ObservableCollection 的 PropertyChanged 事件
处理程序受到保护——除非继承,否则您甚至无法对其做出反应
从课堂上摘下来,然后自己揭露出来。当然,你可以处理
集合中每个项目的 PropertyChanged 事件
您继承的收藏

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.

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)...

The technical problem you are probably facing is that an ObservableCollection only raises notification events if the collection itself changes. i.e. 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.

One solution to this to write a custom class that extends ObservableCollection that does provide this behaviour

From 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. No matter how you change properties of objects within
the collection, the collection's PropertyChanged event will not fire.
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. You could, of course, handle
the PropertyChanged event for each item within the collection from
your inherited collection

萌逼全场 2024-12-06 01:13:21

我赞成 Rachel 的回答,这是 WPF 中对单选按钮或复选框集进行数据绑定的常见方法。如果您仍然想采用树视图方式,下面的代码可以工作。所有与视图相关的代码都在视图中,因此下面的代码遵循 MVVM 原则。如果您是 MVVM 纯粹主义者,则可以将代码放在后面,如果您不希望后面有任何代码,则可以将 TreeView 控件放在用户控件中。

XAML:

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

代码隐藏 + 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 upvoted Rachel's answer, it is a common way in WPF to databind sets of radio buttons or check boxes. If you still want to go the tree view way, below code works. All view related code is in the view, so below code follows MVVM principles. 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.

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:

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; }
}
虚拟世界 2024-12-06 01:13:21

我做了一个技巧,但当我已经选择了一个时,它不起作用
喝,然后我选择另一个而不是我设置的最后选择的值
在可观察集合中设置为 false 但不影响视图
尽管仅在集合中,但选定的复选框仍保持选中状态
一个选项的值为 true。

确保您的孩子反对(可用产品
和 SubProductTypes) 还实现 INotifyPropertyChanged,这将确保 UI 在修改对象时收到更改。

一旦所有对象正确更新 UI,您将能够分层并测试您需要的任何自定义业务逻辑。

因此,如果您的产品类型只能选择一个子类型,则可以在 ProductType 上添加一个名为 OnlyAllowOneChild 的属性。每当子对象引发 IsAssigned 已更改事件时,父对象都可以将所有其他子对象设置为 false。当然,这需要您让父级注册子级的 PropertyChangedEvent,或者获取 EventAggregator(MVVMLight Messenger 或 PRISM EvenAggregator)并创建消息系统。

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.

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.

Once all of you objects update the UI properly you will be able to layer in, and test, whatever custom business logic you need.

So if you have a product type that can only have one sub chosen, you could add a property on ProductType called OnlyAllowOneChild. Whenever, a child object raises a IsAssigned changed event, the parent can set false all other children. 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.

戈亓 2024-12-06 01:13:21

最后我成功解决了我的问题。

在“已分配”属性上,我正在更新数据库值并使用 MVVM Light 消息传递调用视图中的方法,并将当前选定的叶子的父 id 作为参数传递给其中...

在类产品类型中添加了一个属性以展开最后一个的父节点选定的叶子..

在视图的方法中,我正在刷新数据上下文的源并将当前选定的叶子的父ID传递给VM以将其Is Expanded属性值设置为true...

通过此,我的视图与我想要的完全一样...
如果有人有比这更好的解决方案,我会很高兴知道。

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...

Added a property in class Product Types to expand the parent node of the last selected leaf..

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...

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文