我是 WPF/MVVM 的新手,我得到了两个带有两个按钮的列表框,一个用于将所选元素从左侧列表框复制到右侧列表框,然后使用第二个按钮返回。
启动时,左侧列表框正确填充了数据存储中的数据,当我选择一些元素并按下
btn_add_symbol_Click
按钮时,AddSelectedSymbol
方法按预期执行,OnPropertyChanged
以及 WidgetSymbolsSelected
的 getter (我想从右侧的 ListBox) 被调用,返回右侧更新的 CurrentWidget.Symbols
。问题是右侧的列表框lst_SelectedSymbols
始终为空。这是怎么回事?谢谢。
XAML:
<ListBox Grid.Row="1" Grid.Column="0" ItemsSource="{Binding AvailableSymbols}" SelectionMode="Multiple" Name="lst_AvailableSymbols" />
<ListBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding WidgetSymbolsSelected}" SelectionMode="Multiple" Name="lst_SelectedSymbols"/>
虚拟机:
public IEnumerable<string> AvailableSymbols
{
get { return DataManager.AvailableSymbols; }
}
public IEnumerable<string> WidgetSymbolsSelected
{
get { return CurrentWidget.Symbols; }
}
public void AddSelectedSymbol(string symbol)
{
if (!CurrentWidget.Symbols.Contains(symbol)) {
CurrentWidget.Symbols.Add(symbol);
OnPropertyChanged("WidgetSymbolsSelected");
}
}
隐藏代码:
private void btn_add_symbol_Click(object sender, RoutedEventArgs e)
{
var ViewModel = (WidgetEditorViewModel)this.DataContext;
foreach (var Item in lst_AvailableSymbols.SelectedItems)
{
ViewModel.AddSelectedSymbol(Item.ToString());
}
}
实现示例,不保留初级排序
namespace Core2024.SO.DPZ.question79152344
{
public class SomeTextItem
{
private string _title = string.Empty;
private string _text = string.Empty;
private string _description = string.Empty;
public string Title { get => _title; set => _title = value ?? string.Empty; }
public string Text { get => _text; set => _text = value ?? string.Empty; }
public string Description { get => _description; set => _description = value ?? string.Empty; }
}
}
using System.Collections.ObjectModel;
using Simplified; // This is the space with my implementation of INotifyPropertyChanged and ICommand interfaces. You can use your own implementations instead.
namespace Core2024.SO.DPZ.question79152344
{
public class SomeTextViewModel
{
private const string data = "I'm brand new to WPF/MVVM and getting two ListBox wiht two buttons, one to copy selected elements from left listbox to the right one, and back with the second button.";
private static IEnumerable<SomeTextItem> GetItems()
{
string[] split = data.Split();
for (int i = 2; i < split.Length; i += 3)
{
yield return new SomeTextItem()
{
Title = split[i - 2],
Text = split[i - 1],
Description = split[i]
};
}
}
public SomeTextViewModel()
{
AvailableSymbols = new ObservableCollection<SomeTextItem>(GetItems());
WidgetSymbolsSelected = new();
SelectTextItem = new RelayCommand<SomeTextItem>
(
item => { AvailableSymbols.Remove(item); WidgetSymbolsSelected.Add(item); },
item => AvailableSymbols.Contains(item)
);
UnselectTextItem = new RelayCommand<SomeTextItem>
(
item => { WidgetSymbolsSelected.Remove(item); AvailableSymbols.Add(item); },
item => WidgetSymbolsSelected.Contains(item)
);
}
public ObservableCollection<SomeTextItem> AvailableSymbols { get; }
public ObservableCollection<SomeTextItem> WidgetSymbolsSelected { get; }
public RelayCommand SelectTextItem { get; }
public RelayCommand UnselectTextItem { get; }
}
}
<Window x:Class="Core2024.SO.DPZ.question79152344.WidgetSymbolsSelectedWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Core2024.SO.DPZ.question79152344"
mc:Ignorable="d"
Title="WidgetSymbolsSelectedWindow" Height="450" Width="800"
DataContext="{DynamicResource vm}">
<Window.Resources>
<local:SomeTextViewModel x:Key="vm"/>
<DataTemplate DataType="{x:Type local:SomeTextItem}">
<TextBlock>
<Run Text="{Binding Title}"/><Run Text=";"/>
<Run Text="{Binding Text}"/><Run Text=";"/>
<Run Text="{Binding Description}"/><Run Text=";"/>
</TextBlock>
</DataTemplate>
</Window.Resources>
<UniformGrid Columns="3">
<ListBox x:Name="listBox" ItemsSource="{Binding AvailableSymbols}"/>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<Button Content="->" Margin="10" Padding="15 5"
Command="{Binding SelectTextItem, Mode=OneWay}"
CommandParameter="{Binding SelectedItem, ElementName=listBox}"/>
<Button Content="<-" Margin="10" Padding="15 5"
Command="{Binding UnselectTextItem, Mode=OneWay}"
CommandParameter="{Binding SelectedItem, ElementName=listBox1}"/>
</StackPanel>
<ListBox x:Name="listBox1" ItemsSource="{Binding WidgetSymbolsSelected}"/>
</UniformGrid>
</Window>
保留初级排序的实现示例
using Simplified;
namespace Core2024.SO.DPZ.question79152344
{
public class SelectableSomeTextItem : BaseInpc
{
private string _title = string.Empty;
private string _text = string.Empty;
private string _description = string.Empty;
private bool _isSelected;
public string Title { get => _title; set => _title = value ?? string.Empty; }
public string Text { get => _text; set => _text = value ?? string.Empty; }
public string Description { get => _description; set => _description = value ?? string.Empty; }
public bool IsSelected { get => _isSelected; set => Set(ref _isSelected, value); }
}
}
using System.Collections.ObjectModel;
using Simplified; // This is the space with my implementation of INotifyPropertyChanged and ICommand interfaces. You can use your own implementations instead.
namespace Core2024.SO.DPZ.question79152344
{
public class SelectableSomeTextViewModel
{
private const string data = "I'm brand new to WPF/MVVM and getting two ListBox wiht two buttons, one to copy selected elements from left listbox to the right one, and back with the second button.";
private static IEnumerable<SelectableSomeTextItem> GetItems()
{
string[] split = data.Split();
for (int i = 2; i < split.Length; i += 3)
{
yield return new SelectableSomeTextItem()
{
Title = split[i - 2],
Text = split[i - 1],
Description = split[i]
};
}
}
public SelectableSomeTextViewModel()
{
AvailableSymbols = new ObservableCollection<SelectableSomeTextItem>(GetItems());
SelectTextItem = new RelayCommand<SelectableSomeTextItem>
(
item => item.IsSelected = true,
item => AvailableSymbols.Contains(item) && !item.IsSelected
);
UnselectTextItem = new RelayCommand<SelectableSomeTextItem>
(
item => item.IsSelected = false,
item => AvailableSymbols.Contains(item) && item.IsSelected
);
}
public ObservableCollection<SelectableSomeTextItem> AvailableSymbols { get; }
public RelayCommand SelectTextItem { get; }
public RelayCommand UnselectTextItem { get; }
}
}
<Window x:Class="Core2024.SO.DPZ.question79152344.SelectableWidgetSymbolsSelectedWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Core2024.SO.DPZ.question79152344" xmlns:sys="clr-namespace:System;assembly=netstandard"
mc:Ignorable="d"
Title="SelectableWidgetSymbolsSelectedWindow" Height="450" Width="800"
DataContext="{DynamicResource vm}">
<Window.Resources>
<local:SelectableSomeTextViewModel x:Key="vm"/>
<DataTemplate DataType="{x:Type local:SelectableSomeTextItem}">
<TextBlock>
<Run Text="{Binding Title}"/><Run Text=";"/>
<Run Text="{Binding Text}"/><Run Text=";"/>
<Run Text="{Binding Description}"/><Run Text=";"/>
</TextBlock>
</DataTemplate>
<CollectionViewSource x:Key="items.Unselected"
Source="{Binding AvailableSymbols}"
IsLiveFilteringRequested="True"
Filter="OnUnselectedFilter">
<CollectionViewSource.LiveFilteringProperties>
<sys:String>IsSelected</sys:String>
</CollectionViewSource.LiveFilteringProperties>
</CollectionViewSource>
<CollectionViewSource x:Key="items.Selected"
Source="{Binding AvailableSymbols}"
IsLiveFilteringRequested="True"
Filter="OnSelectedFilter">
<CollectionViewSource.LiveFilteringProperties>
<sys:String>IsSelected</sys:String>
</CollectionViewSource.LiveFilteringProperties>
</CollectionViewSource>
</Window.Resources>
<UniformGrid Columns="3">
<ListBox x:Name="listBox"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource items.Unselected}}"/>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<Button Content="->" Margin="10" Padding="15 5"
Command="{Binding SelectTextItem, Mode=OneWay}"
CommandParameter="{Binding SelectedItem, ElementName=listBox}"/>
<Button Content="<-" Margin="10" Padding="15 5"
Command="{Binding UnselectTextItem, Mode=OneWay}"
CommandParameter="{Binding SelectedItem, ElementName=listBox1}"/>
</StackPanel>
<ListBox x:Name="listBox1"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource items.Selected}}"/>
</UniformGrid>
<x:Code>
<![CDATA[
private void OnUnselectedFilter(object sender, System.Windows.Data.FilterEventArgs e)
{
e.Accepted = !((SelectableSomeTextItem)e.Item).IsSelected;
}
private void OnSelectedFilter(object sender, System.Windows.Data.FilterEventArgs e)
{
e.Accepted = ((SelectableSomeTextItem)e.Item).IsSelected;
}
]]>
</x:Code>
</Window>
您可以从这里获取我的基类实现:我的基类实现示例:BaseInpc、RelayCommand、RelayCommandAsync、RelayCommand