基于MVVM模式实现选中控件属性动态设置的技术方案问询
Hey there! Let's break down how to build this feature properly following MVVM principles—no messy code-behind events, clean data binding, and automatic sync between property edits and your UserControls. Here's a step-by-step solution:
1. First, Refactor with a Main Window ViewModel
The biggest issue with your current code is that it uses code-behind for DataContext setup and click events, which violates MVVM. Let's create a MainWindowViewModel to manage the selected item and property collection, plus a helper class to wrap individual properties for the ItemsControl.
Helper Class: PropertyItem (For Two-Way Binding)
This class will bridge your ViewModel properties and the ItemsControl, ensuring edits sync back to the original ViewModel:
public class PropertyItem : Notifier { private readonly object _sourceViewModel; private readonly PropertyInfo _propertyInfo; private string _propertyName; private object _propertyValue; public PropertyItem(object sourceViewModel, PropertyInfo propertyInfo) { _sourceViewModel = sourceViewModel; _propertyInfo = propertyInfo; _propertyName = propertyInfo.Name; _propertyValue = propertyInfo.GetValue(sourceViewModel); } public string PropertyName { get => _propertyName; set { _propertyName = value; OnPropertyChanged(); } } public object PropertyValue { get => _propertyValue; set { _propertyValue = value; OnPropertyChanged(); // Sync the edited value back to the original ViewModel property _propertyInfo.SetValue(_sourceViewModel, Convert.ChangeType(value, _propertyInfo.PropertyType)); } } }
Main Window ViewModel
This will handle tracking the selected ViewModel and generating the property collection for the ItemsControl:
public class MainWindowViewModel : Notifier { private object _selectedItemViewModel; private ObservableCollection<PropertyItem> _properties; // Collection of your ViewModels (Apple + Banana) public ObservableCollection<object> ItemViewModels { get; } = new(); // Track the currently selected ViewModel public object SelectedItemViewModel { get => _selectedItemViewModel; set { _selectedItemViewModel = value; OnPropertyChanged(); // Refresh the property list when selection changes UpdateProperties(); } } // The collection the right-side ItemsControl will bind to public ObservableCollection<PropertyItem> Properties { get => _properties; set { _properties = value; OnPropertyChanged(); } } public MainWindowViewModel() { Properties = new ObservableCollection<PropertyItem>(); // Initialize your ViewModels ItemViewModels.Add(new AppleViewModel { AppleID = "Apple001", Size = 10 }); ItemViewModels.Add(new BananaViewModel { BananaID = "Banana002", Length = 10 }); } private void UpdateProperties() { Properties.Clear(); if (SelectedItemViewModel == null) return; // Get all public properties of the selected ViewModel (skip PropertyChanged) var viewModelProperties = SelectedItemViewModel.GetType() .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.Name != nameof(INotifyPropertyChanged.PropertyChanged)); foreach (var prop in viewModelProperties) { Properties.Add(new PropertyItem(SelectedItemViewModel, prop)); } } }
2. Rewrite the Main Window XAML (MVVM-Friendly)
We'll replace the hardcoded UserControls and MouseDown events with a ListBox—it handles selection out of the box, and we'll use DataTemplates to map ViewModels to their corresponding UserControls:
<Window x:Class="YourNamespace.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:YourNamespace" Title="Property Editor" Height="450" Width="800"> <!-- Set the MainWindowViewModel as the DataContext --> <Window.DataContext> <local:MainWindowViewModel /> </Window.DataContext> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <!-- Left: ListBox to host UserControls (handles selection) --> <ListBox ItemsSource="{Binding ItemViewModels}" SelectedItem="{Binding SelectedItemViewModel}" BorderThickness="0" Margin="10"> <ListBox.ItemTemplate> <DataTemplate> <ContentControl Content="{Binding}"> <ContentControl.Resources> <!-- Map ViewModels to their UserControls --> <DataTemplate DataType="{x:Type local:AppleViewModel}"> <local:AppleControl /> </DataTemplate> <DataTemplate DataType="{x:Type local:BananaViewModel}"> <local:BananaControl /> </DataTemplate> </ContentControl.Resources> </ContentControl> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <!-- Right: Property Editing Area --> <Grid Grid.Column="1" Margin="10"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Text="Properties of Selected Item" Foreground="Blue" FontWeight="SemiBold"/> <ItemsControl Grid.Row="1" ItemsSource="{Binding Properties}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="0,5,0,0"> <TextBlock Text="{Binding PropertyName}" Width="100" Margin="0,0,10,0"/> <!-- TextBox for two-way editing --> <TextBox Text="{Binding PropertyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="150"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid> </Grid> </Window>
3. Clean Up Your UserControls
You don't need to change anything in your Apple/Banana UserControls—their DataContext will automatically be set to the corresponding ViewModel via the DataTemplate, so the existing bindings ({Binding AppleID}, etc.) will work as expected.
4. Key MVVM & Binding Explanations
- Selection Logic: The ListBox's
SelectedItemis bound toSelectedItemViewModelin the main VM—no code-behind events needed. When you click a control, the selection updates automatically. - ItemsSource Binding: The right-side ItemsControl binds directly to the
Propertiescollection in the main VM. This collection refreshes every time the selected ViewModel changes, so the UI updates instantly. - Two-Way Sync: When you edit a value in the TextBox, the
PropertyItemuses reflection to push the change back to the original ViewModel. Since your ViewModels inherit fromNotifier, the UserControl's UI will update automatically.
5. Quick Notes
- Make sure your
Notifierbase class correctly implementsINotifyPropertyChanged—this is critical for all binding updates to work. - If you have ViewModel properties with specific validation (e.g., numeric only), you can add validation logic to the
PropertyItem'sPropertyValuesetter. - This setup is scalable—add a new ViewModel/UserControl pair, and it will automatically show up in the ListBox with its properties in the edit area, no extra code needed.
内容的提问来源于stack exchange,提问作者mumoryan

