WinForms控件与变量关联优化:C# Yahtzee游戏代码改进咨询
嘿,这个场景我太熟悉了——当年我自己写Yahtzee复刻版的时候,也傻乎乎地用了一堆独立的bool变量,结果代码里到处都是重复的判断和事件处理,维护起来头都大!给你几个从简单到进阶的优化方案,帮你把代码捋得清清爽爽:
这是最直接的优化方式,把save1到save5这五个零散的bool变量,换成一个数组或者List<bool>:
// 初始化一个长度为5的数组,默认都是false(未保留) private bool[] _diceSaveStates = new bool[5]; // 或者用List,灵活性稍高 // private List<bool> _diceSaveStates = new List<bool>(new bool[5]);
接下来最爽的是,你不用再写5个几乎一模一样的按钮点击事件了!给所有骰子按钮绑定同一个事件处理方法,然后通过按钮的Tag属性来区分是哪一个骰子(提前给每个按钮的Tag设为0-4的索引):
private void DiceSaveButton_Click(object sender, EventArgs e) { var clickedBtn = sender as Button; if (clickedBtn == null) return; int diceIndex = (int)clickedBtn.Tag; // 切换保留状态 _diceSaveStates[diceIndex] = !_diceSaveStates[diceIndex]; // 统一更新UI,比如用颜色标记保留状态 clickedBtn.BackColor = _diceSaveStates[diceIndex] ? Color.LightGreen : SystemColors.Control; }
这样不管是处理保留状态,还是后续投掷时判断哪些骰子需要重新生成,都可以通过遍历数组来完成,再也不用写一堆if (save1)、if (save2)的重复代码了。
如果想让代码更易维护、扩展性更强,推荐把骰子的状态和行为封装成一个独立的Dice类。这样每个骰子的逻辑都集中在一处,以后加新功能(比如骰子滚动动画、自定义样式)也更方便:
public class Dice { // 骰子当前点数 public int CurrentValue { get; private set; } // 是否被保留 public bool IsSaved { get; private set; } // 关联的UI按钮 private Button _saveButton; private Random _rng = new Random(); public Dice(Button saveButton) { _saveButton = saveButton; // 直接在类内部绑定点击事件 _saveButton.Click += (s, e) => ToggleSaveState(); // 初始化显示 Roll(); } // 切换保留状态并更新UI public void ToggleSaveState() { IsSaved = !IsSaved; _saveButton.BackColor = IsSaved ? Color.LightGreen : SystemColors.Control; } // 投掷骰子(仅当未保留时更新点数) public void Roll() { if (!IsSaved) { CurrentValue = _rng.Next(1, 7); _saveButton.Text = CurrentValue.ToString(); } } // 重置保留状态(比如每轮结束后) public void ResetSaveState() { IsSaved = false; _saveButton.BackColor = SystemColors.Control; } }
然后在你的Form里,用一个集合来管理所有骰子:
private List<Dice> _diceSet = new List<Dice>(); private void YahtzeeForm_Load(object sender, EventArgs e) { // 把五个按钮传入,初始化骰子集合 _diceSet.Add(new Dice(btnSave1)); _diceSet.Add(new Dice(btnSave2)); _diceSet.Add(new Dice(btnSave3)); _diceSet.Add(new Dice(btnSave4)); _diceSet.Add(new Dice(btnSave5)); }
现在投掷按钮的逻辑就变得超级简洁:
private void btnRoll_Click(object sender, EventArgs e) { foreach (var dice in _diceSet) { dice.Roll(); } }
这种方式彻底消除了冗余代码,还让逻辑更清晰——每个骰子自己管自己的状态和行为,Form只需要负责整体流程的控制。
如果你的项目后续要扩展更多功能,或者想让UI和逻辑彻底解耦,可以试试WinForms的数据绑定(虽然不如WPF灵活,但也能实现)。核心思路是创建一个ViewModel类,实现INotifyPropertyChanged,然后把UI控件和ViewModel的属性绑定起来:
public class DiceViewModel : INotifyPropertyChanged { private bool _isSaved; private int _currentValue; private Random _rng = new Random(); public bool IsSaved { get => _isSaved; set { _isSaved = value; OnPropertyChanged(nameof(IsSaved)); } } public int CurrentValue { get => _currentValue; set { _currentValue = value; OnPropertyChanged(nameof(CurrentValue)); } } // 切换保留状态的命令 public ICommand ToggleSaveCommand { get; } public DiceViewModel() { ToggleSaveCommand = new RelayCommand(() => IsSaved = !IsSaved); Roll(); } public void Roll() { if (!IsSaved) { CurrentValue = _rng.Next(1, 7); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } // 简单实现RelayCommand(WinForms没有内置,需要自己写) public class RelayCommand : ICommand { private readonly Action _execute; public event EventHandler CanExecuteChanged; public RelayCommand(Action execute) { _execute = execute; } public bool CanExecute(object parameter) => true; public void Execute(object parameter) => _execute(); }
然后在Form里创建ObservableCollection<DiceViewModel>,把按钮的Command绑定到ToggleSaveCommand,用转换器把IsSaved的bool值转换成背景色。这个方案适合大型项目,代码解耦性更好,但需要花点时间理解数据绑定的逻辑。
个人建议先从方案1入手快速优化代码,等后续需要扩展功能时再逐步过渡到方案2,这样既能快速解决当前的冗余问题,也能为未来的维护打下基础。
内容的提问来源于stack exchange,提问作者cj32

