【问题标题】:C# + WPF : how should I do proper checks/guards/casts when accessing control values?C# + WPF:访问控制值时我应该如何进行适当的检查/保护/强制转换?
【发布时间】:2024-01-20 19:53:01
【问题描述】:

这可能是非常基本的,但我在使用其他语言多年后才开始学习 C#,不幸的是我已经习惯了松散和动态的类型。现在,在构建带有许多复选框的 WPF 表单时,我注意到我的代码用于执行简单的操作,例如确定是否选中了复选框,因为需要检查空值并转换结果,因此实际上非常复杂。我最终得到了辅助函数,例如:

    private bool isChecked(CheckBox control) {
        return control != null && control.IsChecked != null && control.IsChecked.HasValue && (bool)control.IsChecked;
    }

所以在我的逻辑代码中,我可以做到

    if (isChecked(opticsCheckBox))
    {
        // whatever I need to do if opticsCheckBox is checked
    }

这是在 C#(使用 WPF)中做事的正常方式,还是我遗漏了一些简单的东西?基本上,我发现嵌套的条件层一直检查每个对象是否为 null 是错误代码的警告信号(以及我可能忘记检查的事实)。不知道我应该做什么。

我是否应该在任何地方都使用 try ... catch,即使控件不存在或检查并不是真正的异常情况?在我看来,它最终会变得一样混乱。

另一个需要澄清的例子: 当我想写类似的东西时:

    maxWeight = (int)maxWeightComboBox.SelectedItem;

我发现自己在写作:

    if (maxWeightComboBox != null && maxWeightComboBox.SelectedItem != null)
    {
        ComboBoxItem item = (ComboBoxItem)maxWeightComboBox.SelectedItem;
        maxWeight = Int32.Parse(item.Content.ToString());
    }

【问题讨论】:

  • 您可以省略一些测试,但是,是的,欢迎来到 C# 世界! :-)
  • 您没有充分利用 WPF 的数据绑定功能。使用数据绑定和 MVVM 模式,您无需编写您在问题中提供的任何代码。

标签: c# .net wpf casting


【解决方案1】:

一般来说,是的,C# 比动态/有损类型语言更冗长。 Java 也是如此。查看您的具体示例...

private bool isChecked(CheckBox control) {
    return control != null && control.IsChecked != null && control.IsChecked.HasValue && (bool)control.IsChecked;
}

几点...以下两个检查是等价的:

control.IsChecked != null
control.IsChecked.HasValue

IsChecked 属性是 Nullable 类型。由于您是 C# 新手,我建议您阅读值类型与引用类型。一旦掌握了窍门,您就可以了解如何使用 Nullable 类型来包装值类型,以便为其分配空值。下面链接的页面解释了为什么上面的 twp 语句是等效的:

http://msdn.microsoft.com/en-us/library/2cf62fcy%28VS.80%29.aspx

其次,你为什么要检查那个 control!=null ?在典型的场景中,您在 Window 或 UserControl 上使用 XAML 创建控件,通过 x:Name 属性进行标识。在这种情况下,您可以依赖 UI 中存在的控件并放弃此检查。

您的其他两项检查是必要的;-)

好主意将这些放在可以重复使用的方法中。您还可以通过创建扩展方法来“扩展”语言,例如

private bool IsChecked(this CheckBox control) {
    return control.IsChecked.HasValue && (bool)control.IsChecked;
}

// calls the extension method above.
myCheckBox.IsChecked()

希望有帮助。

【讨论】:

  • 我在 XAML 中定义了我的控件,但需要检查 control != null 因为我设置了一个 SelectionChanged 处理程序(用于多个控件),它(我现在看到)被多次调用,因为控件是正在建设中。在最后一个完成填充之前,一些控件为空。这是一个愚蠢的错误。
【解决方案2】:

一个不太冗长的形式是

control != null && control.IsChecked == true

请记住,bool? 具有三个值,true、false 和 null,并且检查单个值总是足够的。例如,a == truea != false 分别是检查 null 何时像 false 或 null 何时像 true。

对于您的组合框示例,我将使用强类型集合开始。有关如何直接绑定到整数的示例,请参阅wpf: how to make ComboBoxItems hold integers in xaml,(或者如果您想要单独的内容/值,请将其绑定到 KeyValuePairs 列表 [例如])然后使用 SelectedValue 和 SelectedValuePath 来减少您的值检索代码。

【讨论】:

  • 另外,我同意 Colin 的观点,即 control != null 通常不应该每次都检查。
  • “一个不那么冗长的形式是 control.IsChecked == true”。你确定不只是“control.IsChecked”?
  • @macias:是的,IsChecked 是一个可为空的布尔值。
  • 整数类型的组合框项目 - 好主意!
【解决方案3】:

对于可空类型(包括引用),您可以使用?? 运算符来指定对象为空时使用的默认值。所以control.IsChecked != null && control.IsChecked 可以替换为control.IsChecked ?? false。这并不能解决您的所有问题,但在某些情况下有助于减少您键入的代码量。

【讨论】:

  • 好提示。我认为我被 HasValue 和铸造文档误导了。这是一个很好的捷径。
【解决方案4】:

WPF 提供诸如属性更改通知、依赖属性和绑定等功能。 所以 WPF 中的良好做法是使用 PresentationModel-View 模式或 MVC 模式,而不是直接访问控件。

您的表示模型(或控制器)必须处理所有业务逻辑,并且视图仅反映模型的实际状态。

在您的案例中,模型如下所示:

public class SampleModel : ObservableObject
{
    private bool? _isFirstChecked;
    public bool? IsFirstChecked
    {
        get
        {
            return this._isFirstChecked;
        }
        set
        {
            if (this._isFirstChecked != value)
            {
                this._isFirstChecked = value;
                this.OnPropertyChanged("IsFirstChecked");
            }
        }
    }

    private int _maxWeight;
    public int MaxWeight
    {
        get 
        {
            return this._maxWeight;
        }
        set 
        {
            if (this._maxWeight != value)
            {
                this._maxWeight = value;
                this.OnPropertyChanged("MaxWeight");
            }
        }
    }

    public IEnumerable<int> ComboBoxItems
    {
        get
        {
            yield return 123;
            yield return 567;
            yield return 999;
            yield return 567;
            yield return 1999;
            yield return 5767;
            yield return 9990;
        }
    }
}

由于我们必须通过属性更改事件通知视图,我们添加了 Observable 类,它实现了这个逻辑:

public class ObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        var safePropertyChanged = this.PropertyChanged;
        if (safePropertyChanged != null)
        {
            safePropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }           
    }
}

所以,现在我们有了带有必要属性声明的表示模型,让我们看看:

<Window x:Class="Test.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:self ="clr-namespace:Test"
    Title="MainWindow" 
    Height="350" Width="525">
<Window.Resources>
    <self:NullableBoolToStringConvreter x:Key="nullableBoolToStringConverter" />
</Window.Resources>
<Grid>
    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <Label VerticalAlignment="Center">IsFirstChecked:</Label>
            <CheckBox VerticalAlignment="Center"
                      IsChecked="{Binding Path=IsFirstChecked}" />
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <Label VerticalAlignment="Center">Max Weight:</Label>
            <ComboBox ItemsSource="{Binding Path=ComboBoxItems}" 
                      VerticalAlignment="Center"
                      SelectedValue="{Binding Path=MaxWeight}">
            </ComboBox>
        </StackPanel>

        <TextBox Text="{Binding Path=MaxWeight}" />
        <TextBox Text="{Binding Path=IsFirstChecked, Converter={StaticResource nullableBoolToStringConverter}}"/>

        <Button Click="Button_Click" Content="Reset combo box to 999 and checkbox to null"/>
    </StackPanel>
</Grid>

我们还要修改后面的这个xaml代码:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        var model = new SampleModel();
        model.MaxWeight = 5767;

        this.Model = model;
    }

    public SampleModel Model
    {
        get
        {
            return (SampleModel)this.DataContext;   
        }
        set 
        {
            this.DataContext = value;
        }
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.Model.MaxWeight = 999;
        this.Model.IsFirstChecked = null;
    }
}

如您所见,我们在 MainWindow 构造函数中创建 SampleModel 实例,设置其属性并将模型实例设置为视图的 DataContext。

DataContext 更改后,WPF 内部机制开始绑定过程。例如,对于组合框控件,它提取模型属性 ComboBoxItems 并创建项目容器。然后提取属性 MaxValue 并将其绑定到 SelectedValue,即组合框选择将指向值“5767”。

出于演示目的,我放置了两个文本框,它们显示“MaxWeight”和“IsFirstChecked”属性的实际值。默认绑定实现在 null 值上显示空字符串,因此我们必须添加适当的转换器:

public class NullableBoolToStringConvreter : IValueConverter
{
    private static readonly string _NullString = "Null";

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value == null ? NullableBoolToStringConvreter._NullString : value.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

您可以测试应用程序并确保 UI 控件状态的更改自动反映在模型中。另一方面,单击按钮会将模型属性重置为定义的值,并且 UI 会立即对其做出反应。

因此,使用 WPF,您无需访问控件。 XAML 和 InitializeComponent() 保证您创建了所有控件。

关于检查:

control.IsChecked.HasValue && (bool)control.IsChecked

如前所述,您可以使用表达式

model.IsFirstChecked ?? false

或扩展方法:

public static class BooleanNullableExtensions 
{
    public static bool IsTrue(this Nullable<bool> value)
    {
        return value.HasValue && value.Value;
    }
}

【讨论】:

  • 相当广泛的答案...只是想提一下 WPF 最常见的结构模式是 MVVM。
  • 我能想象到的最佳答案,写得真好 alex ^_^
  • 出色的清晰和详细的解释。这是我最终想去的方向,但不知道如何从这里到达那里。我认为在我准备好弄清楚如何应用 MVC 或 MVVM 结构之前,我必须处理更多细节,但这表明结构本身可以让我免于许多这些细节。跨度>
【解决方案5】:

有很多方法可以回答您的问题。我认为这些方法中最重要的是强调在 WPF 中,如果您正在编写显式操作 UI 控件的代码,那么您可能做错了什么。

我怎么强调都不过分。在我看来,使用 WPF 的主要原因是它让您不必在代码中操作 UI。

以下是我的程序如何确定是否选中复选框:

在 XAML 中:

<CheckBox IsThreeState="false" IsChecked="{Binding IsChecked, Mode=TwoWay}"/>

在绑定到这个视图的对象中:

public bool IsChecked { get; set; }

我的对象的 IsChecked 属性现在始终反映 UI 中复选框的状态。 (除非我在课堂上实现更改通知,否则反之亦然。)

对于您的组合框示例,我会这样实现它。首先,在 XAML 中:

<ComboBox ItemsSource="{Binding Numbers}" SelectedItem="{Binding SelectedNumber, Mode=TwoWay}"/>

在绑定到视图的对象中:

public IEnumerable<int> Numbers { get { return new[] { 1, 2, 3, 4, 5, 6 }; } }

public int? SelectedNumber { get; set; }

SelectedNumber 在这种情况下可以为空,以便您可以测试未选择任何内容的情况,例如:

Console.WriteLine(SelectedNumber == null
   ? "No number was selected."
   : SelectedNumber + " was selected.);

【讨论】:

  • 另一个很好的答案。我从使用过但没有掌握绑定概念的旧 GUI 库的想法开始。显然,这是一种完全不同的做事方式。
最近更新 更多