WPF互斥列表框 [英] WPF Mutually Exclusive Listboxes

查看:120
本文介绍了WPF互斥列表框的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个具有列表框列表框的应用程序。我想使InnerList箱相互排斥。我的视图模型具有收藏FOOS其中有一个描述,IsSelected属性并有一个名字和一个收藏栏IsSelected属性。

 公共类MyViewModel:INotifyPropertyChanged的
{
     公众的ObservableCollection<富> FOOS {/ * code为简洁起见删除* /}
}公共类Foo:INotifyPropertyChanged的
{
     公共字符串描述{/ * code为简洁起见删除* /}
     公众的ObservableCollection<酒吧GT;酒吧{/ * code为了简洁*删除/}
     公共BOOL IsSelected {/ * code为简洁起见删除* /}
}公共类酒吧:INotifyPropertyChanged的
{
     公共字符串名称{/ * code为简洁起见删除* /}
     公共BOOL IsSelected {/ * code为简洁起见删除* /}
}

下面是我的主窗口的DataContext设置到MyViewModel的一部分。 的ItemsSource = {绑定路径= FOOS} 此ListBox的ItemsSource属性是使用约束,并在模板这个列表框是使用绑定内的ListBox 的ItemsSource ={绑定路径=酒吧}。 A,B和C是FOOS的描述。中包含的这些项目是酒吧的名字。

  | -------------------------- |
| A | -------------------- | |
| |项目1 | |
| |项目2 | |
| |项目3 | |
| | -------------------- | |
| |
| C | -------------------- | |
| |项目X | |
| |项目Y | |
| |商品Z | |
| | -------------------- | |
| |
| B | -------------------- | |
| |项目L | |
| |项目M | |
| | -------------------- | |
| -------------------------- |

我需要它,一个用户只能选择一个项目进行任何酒吧。因此,如果用户从foo的选择项目1然后选择从富B产品点¯x然后从第1应被取消。

我还需要选定项目到窗口上其他一个TextBox控件绑定,但1件事时,我想。

这样做在code和选择变化事件是不是一种选择。我想preFER只保持这种使用XAML。

先谢谢了。

更新

继Moonshield的意见,我想出了这一点,但它仍然是不完全的工作。

 公共类MyViewModel
{
     私人酒吧_selectedBar;     公众的ObservableCollection<富> FOOS {/ * code为简洁起见删除* /}
     公共酒吧SelectedBar
     {
          {返回_selectedBar; }
          组
          {
              _selectedBar = NULL;
              NotifyPropertyChanged(SelectedBar);              _selectedBar =价值;
              NotifyPropertyChanged(SelectedBar);
          }
     }
}

 < ListBox的X:名称=lbFoos的ItemsSource ={绑定路径= FOOS}的SelectedItem ={绑定路径= SelectedBar}>
    < ListBox.ItemContainerStyle>
        <风格的TargetType ={X:输入一个ListBoxItem}>
            < setter属性=模板>
                < Setter.Value>
                    <的ControlTemplate的TargetType ={X:输入一个ListBoxItem}>
                        <&StackPanel的GT;
                            < TextBlock的文本=富/>
                            < TextBlock的文本={绑定路径=说明}/>
                            < ListBox中的ItemsSource ={绑定路径=酒吧}的SelectedItem ={绑定路径=的SelectedItem的RelativeSource = {的RelativeSource FindAncestor,AncestorType = {X:类型列表框}}}>
                                < ListBox.ItemContainerStyle>
                                    <风格的TargetType =ListBoxItem的>
                                        < setter属性=模板>
                                            < Setter.Value>
                                                <的ControlTemplate的TargetType ={X:输入一个ListBoxItem}>
                                                    < TextBlock的文本={绑定路径=名称}/>
                                                < /控件模板>
                                            < /Setter.Value>
                                        < /二传手>
                                        < setter属性=IsSelectedVALUE ={绑定路径= IsSelected,模式= OneWayToSource}/>
                                    < /样式和GT;
                                < /ListBox.ItemContainerStyle>
                            < /列表框>
                        < / StackPanel的>
                    < /控件模板>
                < /Setter.Value>
            < /二传手>
        < /样式和GT;
    < /ListBox.ItemContainerStyle>
< /列表框>


解决方案

做到这一点最简单的方法可能是一个SelectedBar属性添加到您的MyViewModel类,并绑定列表框到的SelectedItem属性。这种只允许一个项目一次选择,并为您提供一些你的文本框绑定到更高版本。

您就可以在每一个ListBoxItem的IsSelected属性设置绑定(OneWayToSource)(通过ItemContainerStyle也许)更新每个栏的IsSelected属性。要更新的Foo对象的IsSelected属性,设置绑定到列表框的的SelectedItem用valueconverter检查它是否为null。

编辑:

SelectedItem属性(实施丹的修复):

 保护栏selectedItem属性;
公共酒吧的SelectedItem {
    得到
    {
        返回将selectedItem;
    }
    组
    {
        将selectedItem = NULL;
        NotifyPropertyChanged(的SelectedItem);        将selectedItem =价值;
        NotifyPropertyChanged(的SelectedItem);
    }

ListBoxItem的与绑定(假设ListBoxItem的DataContext的是酒吧视图模型):

 < ListBoxItem的IsSelected ={绑定路径= IsSelected,模式= OneWayToSource}/>

编辑 - 修复到code:

我设法让你的code工作。有两个问题,我发现:


  1. 项目未出现,选择是,你会重新模板填充与酒吧对象ListBoxItems,所以没有结果时选定的项目没有高亮风格的原因 - 通过设定的ItemTemplate,而不是固定的这一点,该模板项目的内容,而不是覆盖整个模板。


  2. 而不是绑定嵌套列表框父的的SelectedItem指标之一的SelectedItem,然后绑定到视图模型,我改变了绑定,直接绑定到视图模型,其中固定多选问题

     < ListBox的X:名称=lbFoos的ItemsSource ={绑定路径= FOOS}> <! - 移除的SelectedItem结合.-->
        < ListBox.ItemContainerStyle>
            <风格的TargetType ={X:输入一个ListBoxItem}>
                < setter属性=模板>
                    < Setter.Value>
                        <的ControlTemplate的TargetType ={X:输入一个ListBoxItem}>
                            <&StackPanel的GT;
                                < TextBlock的文本=富/>
                                < TextBlock的文本={绑定路径=说明}/>
                                < ListBox中的ItemsSource ={绑定路径=酒吧}的SelectionChanged =ListBox_SelectionChanged的SelectedItem ={绑定路径= DataContext.SelectedBar,的RelativeSource = {的RelativeSource FindAncestor,AncestorType = {X:类型列表框}}}>< ! - 更改绑定到直接绑定到视图模型 - >
                                    < ListBox.ItemTemplate><! - 集ItemTemplated而不是控件模板 - >
                                        <&DataTemplate的GT;
                                            < TextBlock的文本={绑定路径=名称}/>
                                        < / DataTemplate中>
                                    < /ListBox.ItemTemplate>
                                    < ListBox.ItemContainerStyle>
                                        <风格的TargetType =ListBoxItem的>
                                            < setter属性=IsSelectedVALUE ={绑定路径= IsSelected,模式= OneWayToSource}/>
                                        < /样式和GT;
                                    < /ListBox.ItemContainerStyle>
                                < /列表框>
                            < / StackPanel的>
                        < /控件模板>
                    < /Setter.Value>
                < /二传手>
            < /样式和GT;
        < /ListBox.ItemContainerStyle>
    < /列表框>


I have an app that has a ListBox of ListBoxes. I would like to make the InnerList boxes mutually exclusive. My ViewModel has a collection Foos which have a description, an IsSelected property and a collections Bars which have a name and IsSelected property.

public class MyViewModel : INotifyPropertyChanged
{
     public ObservableCollection<Foo> Foos { /* code removed for brevity */ }
}

public class Foo : INotifyPropertyChanged
{
     public string Description { /* code removed for brevity */ }
     public ObservableCollection<Bar> Bars { /* code removed for brevity */ }
     public bool IsSelected { /* code removed for brevity */ }
}

public class Bar : INotifyPropertyChanged
{
     public string Name { /* code removed for brevity */ }
     public bool IsSelected { /* code removed for brevity */ }
}

Below is a part of my MainWindow whose DataContext is set to MyViewModel. This ListBox's ItemsSource property is bound using ItemsSource={Binding Path=Foos} and in the template for this ListBox is an inner ListBox which is bound using ItemsSource="{Binding Path=Bars}". A, B, and C are Foos' Descriptions. The items contained in them are Bar's Names.

|--------------------------|
| A |--------------------| |
|   | Item 1             | |
|   | Item 2             | |
|   | Item 3             | |
|   |--------------------| |
|                          |
| B |--------------------| |
|   | Item X             | |
|   | Item Y             | |
|   | Item Z             | |
|   |--------------------| |
|                          |
| C |--------------------| |
|   | Item l             | |
|   | Item m             | |
|   |--------------------| |
|--------------------------|

I need to make it so a user can only select a single item out of any of the Bars. So, if the user selects Item 1 from Foo A then selects Item X from Foo B then Item 1 should be deselected.

I also need to bind the selected item to a TextBox control elsewhere on the window, but 1 thing a time I suppose.

Doing this in code and selection changed events is not an option. I'd prefer to keep this using XAML only.

Thanks in advance.

UPDATE
Following Moonshield's advice I've come up with this, but it still isn't completely working.

public class MyViewModel
{
     private Bar _selectedBar;

     public ObservableCollection<Foo> Foos { /* code removed for brevity */ }
     public Bar SelectedBar 
     { 
          get { return _selectedBar; }
          set 
          {
              _selectedBar = null;
              NotifyPropertyChanged("SelectedBar");

              _selectedBar = value;
              NotifyPropertyChanged("SelectedBar");
          }
     }    
}

<ListBox x:Name="lbFoos" ItemsSource="{Binding Path=Foos}" SelectedItem="{Binding Path=SelectedBar}">
    <ListBox.ItemContainerStyle>
        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <StackPanel>
                            <TextBlock Text="Foo: " />
                            <TextBlock Text="{Binding Path=Description}" />
                            <ListBox ItemsSource="{Binding Path=Bars}" SelectedItem="{Binding Path=SelectedItem RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ListBox}}}">
                                <ListBox.ItemContainerStyle>
                                    <Style TargetType="ListBoxItem">
                                        <Setter Property="Template">
                                            <Setter.Value>
                                                <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                                    <TextBlock Text="{Binding Path=Name}" />
                                                </ControlTemplate>
                                            </Setter.Value>
                                        </Setter>
                                        <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=OneWayToSource}" />
                                    </Style>
                                </ListBox.ItemContainerStyle>
                            </ListBox>
                        </StackPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

解决方案

The easiest way to do this is probably to add a SelectedBar property to your MyViewModel class and bind the SelectedItem property of the listboxes to that. This only allows one item to be selected at once, and provides you with something to bind your textbox to later.

You can then set a binding (OneWayToSource) on the IsSelected property of each ListBoxItem (via ItemContainerStyle perhaps) to update the IsSelected property of each bar. To update the IsSelected property of the Foo objects, set a binding to the listbox's SelectedItem with a valueconverter to check if it is null.

Edit:

SelectedItem Property (implementing Dan's fix):

protected Bar selectedItem;
public Bar SelectedItem{
    get
    {
        return selectedItem;
    }
    set
    {
        selectedItem = null;
        NotifyPropertyChanged("SelectedItem");

        selectedItem = value;
        NotifyPropertyChanged("SelectedItem");
    }

ListBoxItem with Binding (assuming ListBoxItem DataContext is Bar viewmodel):

<ListBoxItem IsSelected="{Binding Path=IsSelected, Mode=OneWayToSource}" />

Edit - fixes to your code:

I managed to get your code working. Two issues I found:

  1. The reason items weren't appearing to select was that you'd re-templated the ListBoxItems populated with Bar objects, so there was no highlight style when an item was selected - fixed this by setting the ItemTemplate instead, which templates the content of the item rather than overriding the whole template.

  2. Instead of binding the SelectedItem of one of the nested ListBoxes to the SelectedItem index of the parent and then binding that to the viewmodel, I changed the binding to bind directly to the viewmodel, which fixed the multiple-selection issue.

    <ListBox x:Name="lbFoos" ItemsSource="{Binding Path=Foos}"> <!--Removed SelectedItem binding.-->
        <ListBox.ItemContainerStyle>
            <Style TargetType="{x:Type ListBoxItem}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ListBoxItem}">
                            <StackPanel>
                                <TextBlock Text="Foo: " />
                                <TextBlock Text="{Binding Path=Description}" />
                                <ListBox ItemsSource="{Binding Path=Bars}" SelectionChanged="ListBox_SelectionChanged" SelectedItem="{Binding Path=DataContext.SelectedBar, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ListBox}}}"><!--Changed binding to bind directly to ViewModel-->
                                    <ListBox.ItemTemplate><!--Set ItemTemplated rather than ControlTemplate-->
                                        <DataTemplate>
                                            <TextBlock Text="{Binding Path=Name}" />
                                        </DataTemplate> 
                                    </ListBox.ItemTemplate>
                                    <ListBox.ItemContainerStyle>
                                        <Style TargetType="ListBoxItem">
                                            <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=OneWayToSource}" />
                                        </Style>
                                    </ListBox.ItemContainerStyle>
                                </ListBox>
                            </StackPanel>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>
    

这篇关于WPF互斥列表框的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆