C # + WPF: как я должен делать правильные проверки / охранники / приведение при доступе к контрольным значениям? - PullRequest
1 голос
/ 19 января 2011

Это, вероятно, довольно просто, но я просто забираю 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) или я упускаю что-то простое? По сути, я нахожу вложенные слои условных выражений, которые все время проверяют каждый объект на наличие нуля, чтобы быть предупреждением о плохом коде (и факте, что я мог забыть проверку). Не уверен, что мне следует делать.

Должен ли я использовать try ... catch везде, даже если элемент управления отсутствует или не проверен, не является ли исключительным условием? Мне кажется, что в итоге все будет так же беспорядочно.

Еще один пример для уточнения: Когда я хотел бы написать что-то вроде:

    maxWeight = (int)maxWeightComboBox.SelectedItem;

Вместо этого я пишу:

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

Ответы [ 5 ]

3 голосов
/ 19 января 2011

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;
    }
}

Как вы можете видеть, мы создаем экземпляр SampleModel в конструкторе MainWindow, настраиваем его свойства и устанавливаем экземпляр модели как DataContext представления.

После изменения DataContext внутренний механизм WPF начинает процесс привязки. Например, для элемента управления combobox он извлекает свойство модели ComboBoxItems и создает контейнеры элементов. Затем извлекает свойство MaxValue и привязывает его к SelectedValue, то есть выделение в выпадающем списке будет указывать на значение «5767».

В целях демонстрации я поместил два текстовых поля, которые отображают фактическое значение свойств "MaxWeight" и "IsFirstChecked". Реализация привязки по умолчанию показывает пустые строки с нулевыми значениями, поэтому мы должны добавить соответствующий конвертер:

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();
    }
}

Вы можете протестировать приложение и убедиться, что изменения состояния элементов управления пользовательского интерфейса автоматически отражаются в модели. С другой стороны, нажатие на кнопку сбрасывает свойства модели до определенных значений, и пользовательский интерфейс немедленно реагирует на это.

Так что с 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;
    }
}
1 голос
/ 19 января 2011

Есть много способов ответить на ваш вопрос. Я думаю, что наиболее важным из этих способов является подчеркнуть, что в WPF, если вы пишете код, который явно манипулирует элементами управления пользовательского интерфейса, вы, вероятно, делаете что-то не так.

Я не могу подчеркнуть это достаточно. На мой взгляд, основная причина использования WPF вообще заключается в том, что он освобождает вас от необходимости манипулировать пользовательским интерфейсом в коде.

Вот как мои программы определяют, установлен ли флажок или нет:

В XAML:

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

В объекте, связанном с этим видом:

public bool IsChecked { get; set; }

Свойство IsChecked моего объекта теперь всегда отражает состояние флажка в пользовательском интерфейсе. (Обратное неверно, если в моем классе не реализовано уведомление об изменениях.)

Для вашего примера со списком я бы реализовал это следующим образом. Во-первых, в 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.);
1 голос
/ 19 января 2011

В общем, да, 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

Во-вторых, почему вы проверяете этот элемент управления! = Null?В типичных сценариях вы создаете элементы управления в XAML в вашем Window или UserControl, идентифицируя их с помощью атрибута x: Name.В этом случае вы можете положиться на элемент управления, присутствующий в вашем пользовательском интерфейсе, и отбросить эту проверку.

Необходимы две другие проверки ;-)

Хорошая идея добавить их в метод, который вы можете использовать.повторное использование.Вы также можете «расширить» язык, создав методы расширения, например,

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

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

Hoep, который помогает.

0 голосов
/ 19 января 2011

При использовании типов, допускающих значения NULL (включая ссылки), вы можете использовать оператор ??, чтобы указать значение по умолчанию, которое будет использоваться, если объект является нулевым.Таким образом, control.IsChecked != null && control.IsChecked можно заменить на control.IsChecked ?? false.Это не решает всех ваших проблем, но в некоторых случаях помогает уменьшить объем набираемого вами кода.

0 голосов
/ 19 января 2011

Менее многословная форма -

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

Помните, что bool? имеет три значения: true, false и null, и всегда достаточно проверить одно значение. Например, a == true и a != false являются проверками, соответственно, для случаев, когда нулевое значение работает как ложное, или когда нулевое значение работает как истинное.

Для вашего примера со списком я бы начал использовать строго типизированную коллекцию. См. wpf: как сделать так, чтобы ComboBoxItems содержал целые числа в xaml , для примера того, как связать напрямую с целыми числами (или, если вы хотите отдельно Content / Value, связать его со списком KeyValuePairs [например]) Затем используйте SelectedValue и SelectedValuePath, чтобы уменьшить ваш код поиска значения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...