如何在WPF中基于MVVM实现二十一点游戏的设置菜单?
WPF二十一点游戏设置菜单的MVVM实现方案
我要给WPF平台的二十一点卡牌游戏做设置菜单,大部分用TextBox,少数用CheckBox、DatePicker或更复杂控件。不想硬编码违反MVVM原则,要做到视图不知晓选项内容,视图模型/模型不关心展示方式。
已经实现了Option<T>类:
public class Option<T> { public string Name { get; set; } public T DefaultValue { get; } /// <summary> /// The current value /// </summary> public T Value { get; set; } public Option(string name, T defaultValue) { Name = name; DefaultValue = defaultValue; Value = defaultValue; } public Option(string name, T defaultValue, T value) : this(name, defaultValue) { Value = value; } /// <summary> /// Resets the value to default /// </summary> public void Reset() => Value = DefaultValue; }
现在碰到两个核心问题:
- 无法存储不同类型的设置项列表,只能存放特定类型的选项,要不要把
<T>替换成object? - 已经做了
OptionBaseUserControl,包含通用元素:
<TextBlock Text="{Binding Name}" FontSize="50" VerticalAlignment="Center" HorizontalAlignment="Center"/> <ContentControl Grid.Column="1" Content="{Binding Option}"/> <Button Grid.Column="2" Content="OK" FontWeight="Light" Command="{Binding ConfirmCommand}"/> <Button Grid.Column="3" Content="↻" Command="{Binding ResetCommand}"/>
原本打算做TextBoxOption这类视图放到ContentControl里,但不知道怎么指定某个设置项视图模型对应哪个视图。
问题1:存储不同类型的Option列表
不要直接用object,而是给Option<T>添加非泛型基类OptionBase,这样就能用List<OptionBase>统一存储所有设置项,同时保留强类型特性:
using System.ComponentModel; using System.Runtime.CompilerServices; public abstract class OptionBase : INotifyPropertyChanged { public string Name { get; set; } public abstract object CurrentValue { get; set; } public abstract object DefaultValue { get; } public abstract void Reset(); public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class Option<T> : OptionBase { private T _value; public new T DefaultValue { get; } public override object CurrentValue { get => _value; set { if (value is T typedValue && !EqualityComparer<T>.Default.Equals(_value, typedValue)) { _value = typedValue; OnPropertyChanged(); } } } public override object DefaultValue => DefaultValue; public Option(string name, T defaultValue) { Name = name; DefaultValue = defaultValue; _value = defaultValue; } public Option(string name, T defaultValue, T value) : this(name, defaultValue) { _value = value; } public override void Reset() { _value = DefaultValue; OnPropertyChanged(nameof(CurrentValue)); } }
问题2:视图模型与视图的自动匹配
用WPF的**数据模板(DataTemplate)**自动绑定,无需手动指定对应关系,步骤如下:
- 为每种
Option<T>类型定义专属DataTemplate,放到App.xaml或OptionBase所在UserControl的资源中:
<!-- 字符串类型对应TextBox --> <DataTemplate DataType="{x:Type local:Option`1[System.String]}"> <TextBox Text="{Binding CurrentValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> </DataTemplate> <!-- 布尔类型对应CheckBox --> <DataTemplate DataType="{x:Type local:Option`1[System.Boolean]}"> <CheckBox IsChecked="{Binding CurrentValue, Mode=TwoWay}"/> </DataTemplate> <!-- 日期类型对应DatePicker --> <DataTemplate DataType="{x:Type local:Option`1[System.DateTime]}"> <DatePicker SelectedDate="{Binding CurrentValue, Mode=TwoWay}"/> </DataTemplate>
- 调整
OptionBase的ContentControl绑定,直接绑定到当前视图模型实例(即OptionBase的DataContext):
<ContentControl Grid.Column="1" Content="{Binding}"/>
WPF会自动根据Content的类型匹配对应的DataTemplate渲染视图。
额外优化:全局设置管理视图模型
创建SettingsViewModel统一管理所有设置项,包含全局确认、重置命令:
using System.Collections.ObjectModel; using System.Windows.Input; public class SettingsViewModel { public ObservableCollection<OptionBase> Options { get; } = new ObservableCollection<OptionBase>(); public ICommand ConfirmCommand { get; } public ICommand ResetAllCommand { get; } public SettingsViewModel() { // 添加示例设置项 Options.Add(new Option<string>("玩家名称", "默认玩家")); Options.Add(new Option<bool>("音效开启", true)); Options.Add(new Option<DateTime>("生日", DateTime.Now)); ConfirmCommand = new RelayCommand(SaveSettings); ResetAllCommand = new RelayCommand(ResetAllOptions); } private void SaveSettings() { // 实现保存逻辑:写入配置文件/本地存储等 } private void ResetAllOptions() { foreach (var option in Options) option.Reset(); } } // 简易RelayCommand实现 public class RelayCommand : ICommand { private readonly Action _execute; private readonly Func<bool> _canExecute; public event EventHandler CanExecuteChanged; public RelayCommand(Action execute, Func<bool> canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true; public void Execute(object parameter) => _execute(); public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); }
然后在主设置页面用ItemsControl渲染所有设置项:
<ItemsControl ItemsSource="{Binding Options}"> <ItemsControl.ItemTemplate> <DataTemplate> <local:OptionBase Margin="0 5"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
这样整个架构完全符合MVVM:视图仅负责展示,视图模型管理设置逻辑,模型存储数据,各模块完全解耦。
内容的提问来源于stack exchange,提问作者Crocialist
相关产品推荐
相关产品推荐

