问题
OK I have a weird one here. What I am trying to figure out is how to have one listview, populated by an ObservableCollection, update another ListView, populated by another ObservableCollection, based on the first listview's selection and then use a valueconverter to check or uncheck a checkbox based on the selection combined with the current item in the second listview. This part I somewhat have working by using multibinding, but the part that has me stumped is when I check or uncheck an item in the second listview I need to be able to grab that event and all the currently checked items in that listview an update a database field based on that.
I know this may not make a lot of sense and I'm struggling on how to make it clearer, but below is the xaml for the two listviews and the code for the converter. I can see that when I check or uncheck a box in the secodn listview that the converter tries to do the ConvertBack method which blows up, but if I set it to just return null then the code no longer blows up, but the checkbox is highlighted in red like a validation error has occured.
I am not even sure that multibinding is the way to go here and I have looked at Josh Smith's multiselection listview stuff, but with the conversion needed I can not see how to implement that successfully either.
If anyone has any ideas I would greatly appreciate it. Also my apologies if I didn't explain my need very clearly, but I am hoping with the chaotic description and the code you can kind of see where I am going with it.
Thanks in advance!
First ListView that feeds the second one
<Grid>
<ListView x:Name="listRule" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="3,3,3,3" ItemsSource="{Binding RuleListing}" exts:Selected.Command="{Binding RuleSelectedCommand}" SelectedIndex="0">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=DisplayName}" ToolTip="{Binding Path=Expression}" FontWeight="Bold"/>
<TextBlock Text=" ( "/>
<TextBlock Text="{Binding Description}" FontStyle="Italic" />
<TextBlock Text=" )"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Second ListView with converter and multibinding
<Grid HorizontalAlignment="Stretch">
<Grid.Resources>
<converters:RuleToRoleBooleanConverter x:Key='RuleRoleConverter' />
<DataTemplate x:Key="RoleTemplate">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" MinWidth="200"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding RoleName}" HorizontalAlignment="Left" Margin="3,0,0,0" Grid.Column="0" />
<CheckBox HorizontalAlignment="Right" Margin="0,0,3,0" Grid.Column="1">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource RuleRoleConverter}">
<Binding ElementName="listRule" Path="SelectedItem" />
<Binding Path="RoleName"/>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</Grid>
</DataTemplate>
</Grid.Resources>
<ListView Name="listRoles" ItemsSource="{Binding RoleListing}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
SelectionMode="Multiple" ItemTemplate="{StaticResource ResourceKey=RoleTemplate}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsRoleSelected}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
Value Converter
public class RuleToRoleBooleanConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] != null && values[1] != null)
{
string expression = ((EliteExtenderRule)values[0]).Expression;
string role = values[1].ToString();
if (expression.Contains("R:*") || expression.Contains("R:" + role))
{
return true;
}
}
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;// new object[] { (bool)value, null };
}}
回答1:
I might have a solution for the first part of your problem, i.e "to figure out is how to have one listview, populated by an ObservableCollection, update another ListView, populated by another ObservableCollection, based on the first listview's selection".
But before, for this part of the problem dit does just bind the target ListView on he selected items property of the source ListView would satisfy your needs ?
<ListView x:Name="listOne" Grid.Column="0" Width="50" Height="200" ItemsSource="{Binding SourceList}" SelectionMode="Extended" />
<ListView x:Name="listTwo" Grid.Column="1" Width="50" Height="200" ItemsSource="{Binding ElementName=listOne, Path=SelectedItems}" />
If not, I had a similar problem on recent project and I came up with a behavior applied to source list.
Saying that you have a view model like that as the DataContext of your view :
public ObservableCollection<BusinessAdapter> SourceList { get; private set; }
public ObservableCollection<BusinessAdapter> TargetList { get; private set; }
public Window1()
{
InitializeComponent();
DataContext = this;
SourceList = new ObservableCollection<BusinessAdapter>();
TargetList = new ObservableCollection<BusinessAdapter>();
for (int i = 0; i < 50; i++)
{
SourceList.Add(new BusinessAdapter { BusinessProperty = "blabla_" + i });
}
}
And in your view you have something like that :
<ListView x:Name="listOne" Grid.Column="0" Width="50" Height="200" ItemsSource="{Binding SourceList}" SelectionMode="Extended" />
<ListView x:Name="listTwo" Grid.Column="1" Width="50" Height="200" ItemsSource="{Binding TargetList}" />
Then you can attach this behavior on the source listbox and pass the target view model source to it.
==> the behavior:
public class ListBoxMultiSelectionBehavior
{
public static IList<BusinessAdapter> GetTargetList(DependencyObject obj)
{
return (IList<BusinessAdapter>)obj.GetValue(TargetListProperty);
}
public static void SetTargetList(DependencyObject obj, IEnumerable<BusinessAdapter> value)
{
obj.SetValue(TargetListProperty, value);
}
// Using a DependencyProperty as the backing store for Adapter. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TargetListProperty =
DependencyProperty.RegisterAttached("TargetList", typeof(IList<BusinessAdapter>), typeof(ListBoxMultiSelectionBehavior), new UIPropertyMetadata(null, OnListChanged));
/// <summary>
/// Model List changed callback
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var mSelector = d as ListView ;
if (mSelector != null)
{
mSelector.SelectionChanged -= MSelectorSelectionChanged;
//Multiple or Extented selection mode are mandatory
if (mSelector.SelectionMode == SelectionMode.Single)
{
return;
}
mSelector.SelectedItems.Clear();
//"binding" => model to view
//get the model's list
var a = GetTargetList(d);
if (a != null)
{
//for each model in the list
foreach (var ba in a)
{
//in the listbox items collection
foreach (var item in mSelector.Items)
{
//find the correspondance and if found
if (((BusinessAdapter)item).BusinessProperty == ba.BusinessProperty)
{
//add item to selected items
mSelector.SelectedItems.Add(item);
}
}
}
}
//"binding" => view to model
//subscribe to changes in selection in the listbox
mSelector.SelectionChanged += MSelectorSelectionChanged;
}
}
static void MSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e != null)
{
var adapter = GetTargetList(sender as DependencyObject);
var list = adapter;//copy
if (e.RemovedItems.Count > 0 /*&& e.RemovedItems.Count != list.Count*/)
{
foreach (var ba in e.RemovedItems.Cast<BusinessAdapter>())
{
list.Remove(ba);
}
}
if (e.AddedItems.Count > 0)
{
foreach (var ba in e.AddedItems.Cast<BusinessAdapter>())
{
list.Add(ba);
}
}
}
}
}
What it does basically is that when initializing, he retreive the target list items and populates the source list (one way), and subscribe to list box source selection changed event to populate target list (the other way). Then, the target listbox gest updated when items are added in the bahavior.
The new XAML looks just like that :
<ListView x:Name="listOne" Grid.Column="0" Width="50" Height="200" ItemsSource="{Binding SourceList}" SelectionMode="Extended" StackOverflow:ListBoxMultiSelectionBehavior.TargetList="{Binding TargetList}" />
<ListView x:Name="listTwo" Grid.Column="1" Width="50" Height="200" ItemsSource="{Binding TargetList}" />
For the second part of your problem, I don't have something that come to my mind right now... sorry :/
来源:https://stackoverflow.com/questions/3566658/multibinding-multiselection-listview