You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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;
}

现在碰到两个核心问题:

  1. 无法存储不同类型的设置项列表,只能存放特定类型的选项,要不要把<T>替换成object?
  2. 已经做了OptionBase UserControl,包含通用元素:
<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)**自动绑定,无需手动指定对应关系,步骤如下:

  1. 为每种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>
  1. 调整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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 12:37:17