0

Пишу сейчас тестовый проект на Wpf C# с использованием MVVM. До этого работал с WinForm, поэтому для меня отсутствие обработчиков событий в новинку. Программа должна считать значение функции, типов которой имеется 5 штук. Есть две модели: Variables:

public class Variables : NotifyPropertyChanged
{
    private int _x;
    private int _y;
    private double _f;
public Variables()
{
    X = 0;
    Y = 0;
    _f = 0;
}

public int X
{
    get => _x;
    set
    {
        _x = value;
        OnPropertyChanged("F");
    }
}

public string XChecker
{
    get => X.ToString();
    set
    {
        int index = value.Length - 1;
        if (index == -1)
        {
            X = 0;
        }
        else if (value[index] >= 48 && value[index] <= 57 && index >= 0)
        {
            X = int.Parse(value);
        }
        OnPropertyChanged("VariablesSetsList");
    }
}

public int Y
{
    get => _y;
    set
    {
        _y = value;
        OnPropertyChanged("F");
    }
}

public string YChecker
{
    get => Y.ToString();
    set
    {
        int index = value.Length - 1;
        if (index == -1)
        {
            Y = 0;
        }
        else if (value[index] >= 48 && value[index] <= 57 && index >= 0)
        {
            Y = int.Parse(value);
        }
        OnPropertyChanged("CurrentVariablesSet");
    }
}

public double F
{
    get => _f;
    set
    {
        _f = value;

        OnPropertyChanged("F");
    }
}

public void SetF(int a, int b, int c, string funcType)
{
    int n = 0;
    switch (funcType)
    {
        case "Линейная":
            n = 1;
            break;
        case "Квадратичная":
            n = 2;
            break;
        case "Кубическая":
            n = 3;
            break;
        case "4-ой степени":
            n = 4;
            break;
        case "5-ой степени":
            n = 5;
            break;
        default:
            break;
    }
    _f = (a * Math.Pow(X, n)) + (b * Math.Pow(Y, n - 1)) + c;
    OnPropertyChanged("F");
}

}

И FuncModel:

public class FuncTemplate : NotifyPropertyChanged
{
    private string _funcType;
    private int _a;
    private int _b;
    private List<int> _c;
    private int _currentC;
    private ObservableCollection<Variables> _variablesSetsList;
    private Variables _currentVariablesSet;
public FuncTemplate(string funcType)
{
    _funcType = funcType;
    C = new List&lt;int&gt;();
    _variablesSetsList = new ObservableCollection&lt;Variables&gt;();
    _currentVariablesSet = new Variables();
}

public string FuncType
{
    get =&gt; _funcType;
    set
    {
        _funcType = value;
        OnPropertyChanged(&quot;FuncType&quot;);
    }
}

public int A
{
    get =&gt; _a;
    set
    {
        _a = value;
        OnPropertyChanged(&quot;A&quot;);
    }
}

public string AChecker
{
    get =&gt; A.ToString();
    set
    {
        int index = value.Length - 1;
        if (index == -1)
        {
            A = 0;
        }
        else if (value[index] &gt;= 48 &amp;&amp; value[index] &lt;= 57 &amp;&amp; index &gt;= 0)
        {
            A = int.Parse(value);
        }
        OnPropertyChanged(&quot;A&quot;);
    }
}

public int B
{
    get =&gt; _b;
    set
    {
        _b = value;
        OnPropertyChanged(&quot;B&quot;);
    }
}

public string BChecker
{
    get =&gt; B.ToString();
    set
    {
        int index = value.Length - 1;
        if (index == -1)
        {
            B = 0;
        }
        else if (value[index] &gt;= 48 &amp;&amp; value[index] &lt;= 57 &amp;&amp; index &gt;= 0)
        {
            B = int.Parse(value);
        }
        OnPropertyChanged(&quot;B&quot;);
    }
}

public List&lt;int&gt; C
{
    get =&gt; _c;
    set
    {
        switch (FuncType)
        {
            case &quot;Линейная&quot;:
                _c = new List&lt;int&gt; { 1, 2, 3, 4, 5 };
                break;
            case &quot;Квадратичная&quot;:
                _c = new List&lt;int&gt; { 10, 20, 30, 40, 50 };
                break;
            case &quot;Кубическая&quot;:
                _c = new List&lt;int&gt; { 100, 200, 300, 400, 500 };
                break;
            case &quot;4-ой степени&quot;:
                _c = new List&lt;int&gt; { 1000, 2000, 3000, 4000, 5000 };
                break;
            case &quot;5-ой степени&quot;:
                _c = new List&lt;int&gt; { 10000, 20000, 30000, 40000, 50000 };
                break;
            default:
                break;
        }
        OnPropertyChanged(&quot;C&quot;);
    }
}

public int CurrentC
{
    get =&gt; _currentC;
    set
    {
        _currentC = value;
        OnPropertyChanged(&quot;CurrentC&quot;);
    }
}

/// &lt;summary&gt;
/// Свойство поля _variablesSetsList, являющегося коллекцией наборов 
/// значений переменных x, y, f. При установке значения оповещает 
/// систему об изменении свойства. 
/// &lt;/summary&gt;
public ObservableCollection&lt;Variables&gt; VariablesSetsList
{
    get =&gt; _variablesSetsList;
    set
    {
        _variablesSetsList = value;
        OnPropertyChanged(&quot;VariablesSetsList&quot;);
    }
}

/// &lt;summary&gt;
/// Свойство поля _currentVariablesSet, содержащего выбранный набор переменных.
/// В сеттере проверяет, существует ли набор, и либо создает его, 
/// либо устанавливает значения в существующий, после чего оповещает
/// систему об изменении свойства. 
/// &lt;/summary&gt;
public Variables CurrentVariablesSet
{
    get =&gt; _currentVariablesSet;
    set
    {
        if (value == null)
            _currentVariablesSet = new Variables();
        else
        {
            _currentVariablesSet = value;
            _currentVariablesSet.SetF(A, B, CurrentC, FuncType);
        }
        OnPropertyChanged(&quot;CurrentVariablesSet&quot;);
    }
}

}

Вторая - шаблон функции, а первая содержит свой набор переменных для каждой функции.

Также есть View:

<Grid >
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="190"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="150"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
&lt;StackPanel
    Name=&quot;FuncTypesListStackPanel&quot;
    Grid.Row=&quot;0&quot; 
    Grid.Column=&quot;0&quot;&gt;
    &lt;Label 
        Content=&quot;Тип функции:&quot;
        Name=&quot;FuncTypeLabel&quot;
        Grid.Row=&quot;0&quot;
        Grid.Column=&quot;0&quot;
        FontWeight=&quot;Bold&quot;
        FontSize=&quot;12&quot;
        HorizontalAlignment=&quot;Center&quot; 
        Margin=&quot;10,0,10,0&quot; 
        VerticalAlignment=&quot;Top&quot;/&gt;
    &lt;ListBox 
        Name=&quot;FunctionTypesListBox&quot;
        VerticalAlignment=&quot;Bottom&quot;
        Margin =&quot;10,0,10,10&quot;
        ItemsSource=&quot;{Binding Functions}&quot;
        SelectedItem=&quot;{Binding CurrentFunction}&quot;&gt;
        &lt;ListBox.ItemTemplate&gt;
            &lt;DataTemplate&gt;
                &lt;TextBlock FontSize=&quot;12&quot; Text=&quot;{Binding Path=FuncType}&quot;/&gt;
            &lt;/DataTemplate&gt;
        &lt;/ListBox.ItemTemplate&gt;
    &lt;/ListBox&gt;
&lt;/StackPanel&gt;

&lt;StackPanel
    Name=&quot;CurrentFuncFactorsStackPanel&quot;
    Grid.Row=&quot;0&quot;
    Grid.Column=&quot;1&quot;
    DataContext=&quot;{Binding CurrentFunction}&quot;&gt;

    &lt;Label
        Content=&quot;A:&quot;
        Name=&quot;FactorALabel&quot;
        FontSize=&quot;12&quot;
        FontWeight=&quot;Bold&quot;
        HorizontalAlignment=&quot;Left&quot;
        Margin=&quot;10,0,0,0&quot;/&gt;
    &lt;TextBox
        Name=&quot;FactorATexBox&quot;
        HorizontalAlignment=&quot;Stretch&quot;
        Margin=&quot;10,0,150,0&quot;
        VerticalContentAlignment=&quot;Top&quot;
        FontSize=&quot;12&quot;
        Text=&quot;{Binding AChecker, UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
    &lt;Label 
        Content=&quot;B:&quot;
        Name=&quot;FactorBLabel&quot;
        FontSize=&quot;12&quot;
        FontWeight=&quot;Bold&quot;
        HorizontalAlignment=&quot;Left&quot;
        Margin=&quot;10,0,0,0&quot;/&gt;
    &lt;TextBox
        Name=&quot;FactorBTexBox&quot;
        HorizontalAlignment=&quot;Stretch&quot;
        Margin=&quot;10,2,150,2&quot;
        VerticalContentAlignment=&quot;Center&quot;
        FontSize=&quot;12&quot;
        Text=&quot;{Binding BChecker, UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
    &lt;Label 
        Content=&quot;C&quot;
        Name=&quot;FactorCLabel&quot;
        FontSize=&quot;12&quot;
        FontWeight=&quot;Bold&quot;
        HorizontalAlignment=&quot;Left&quot;
        Margin=&quot;10,0,0,0&quot;/&gt;

    &lt;ComboBox
        Name=&quot;FactorCComboBox&quot;
        HorizontalAlignment=&quot;Stretch&quot;
        Margin=&quot;10,2,150,2&quot;
        FontSize=&quot;12&quot;
        VerticalContentAlignment=&quot;Top&quot;
        ItemsSource=&quot;{Binding C}&quot;
        SelectedItem=&quot;{Binding CurrentC}&quot;&gt;
        &lt;ComboBox.ItemTemplate&gt;
            &lt;DataTemplate&gt;
                &lt;TextBlock FontSize=&quot;12&quot; Text=&quot;{Binding}&quot;/&gt;
            &lt;/DataTemplate&gt;
        &lt;/ComboBox.ItemTemplate&gt;
    &lt;/ComboBox&gt;
&lt;/StackPanel&gt;

&lt;ScrollViewer 
    Name=&quot;TableScrollViewer&quot;
    Grid.Row=&quot;1&quot;
    Grid.Column=&quot;0&quot;
    Grid.ColumnSpan=&quot;2&quot;
    DataContext=&quot;{Binding CurrentFunction}&quot;
    &gt;
    &lt;DataGrid 
        Name=&quot;TableDataGrid&quot;
        ColumnHeaderHeight=&quot;25&quot;
        CanUserAddRows=&quot;True&quot;
        ItemsSource=&quot;{Binding VariablesSetsList}&quot;
        SelectedItem =&quot;{Binding CurrentVariablesSet}&quot;
        AutoGenerateColumns=&quot;False&quot;&gt;

        &lt;DataGrid.Columns&gt;

            &lt;DataGridTextColumn Header=&quot;x&quot;
                                Binding=&quot;{Binding XChecker, UpdateSourceTrigger=PropertyChanged}&quot; 
                                Width=&quot;*&quot;/&gt;
            &lt;DataGridTextColumn Header=&quot;y&quot;
                                Binding=&quot;{Binding YChecker, UpdateSourceTrigger=PropertyChanged}&quot; 
                                Width=&quot;*&quot;/&gt;
            &lt;DataGridTextColumn Header=&quot;f(x,y)&quot;
                                IsReadOnly = &quot;True&quot;  
                                Binding=&quot;{Binding F}&quot; 
                                Width=&quot;*&quot;&gt;
            &lt;/DataGridTextColumn&gt;
        &lt;/DataGrid.Columns&gt;
    &lt;/DataGrid&gt;
&lt;/ScrollViewer&gt;

</Grid>

Т.е. последний DataGrid представляет собой таблицу значений для X, Y, F. F должна высчитываться автоматически, сам столбец доступен только на чтение. И я не могу понять, как его таким сделать? Пробовал функцию подсчёта прописать в сеттер выбранного набора переменных, но тогда значение F меняется только при перещелкивании с на другую строку таблицы и обратно. в сеттер F прописать её не могу, так как оттуда нет доступа к коэффициентом A,B,C и типу функции. Подскажите, что можно с этим сделать? Может, я как-то неправильно использую Binding? Уже две недели не могу решить эту проблему.

  • У вас, как по мне, есть существенный просчет в архитектуре проекта. Внимание на класс Variables, для начала вы его назвали "Переменные", то есть это некий список различных переменных. Далее смотрим на X и на Y, видим, что они идентичны, как в плане "чекера", так и в плане переменной. Аналогично кстати и у класса FuncTemplate. Все это сводится к тому, что вам надо сделать класс, который будет отвечать за одну переменную, и заполнить далее простую коллекцию ими, ну а дальше, можно сделать уже такое. – EvgeniyZ Aug 30 '23 at 17:15
  • 1
    Спасибо, очень помог комментарий и оставленная ссылка. – Yarmak42 Aug 31 '23 at 09:03

1 Answers1

0

Предлагаю попробовать следующее. Используйте для класса

public class Variables

следующего предка:

using System.Runtime.CompilerServices;

#region -- Общие типы данных /// <summary> /// Базовый класс одной записи, реализующий необходимый функционал оповещения о произведённых изменениях. Класс не содержит никаких полей. /// </summary> public class myRecord : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// Рассылка оповещения о внесении изменения в данные. В качестве параметра (если он не задан принудительно) /// автоматически подставляется имя свойства, откуда происходит вызов. /// </summary> /// <param name="propertyName"></param> public void Notify([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } #endregion

далее в каждом сеттере просто пишите в конце (код примера взят у Вас):

public int X
{
    get => _x;
    set
    {
        _x = value;
        //-- **НЕ ТАК** OnPropertyChanged("F"); 
        Notify(); //<<<< для обновления текущего поля
        //-- Если при установке значения нужно обновить еще несколько полей, то добавляем для каждого:
        Notify("_имя поля_");
        //-- ... а ещё лучше делать так:
        Notify(nameof(имя_класса.имя_поля));
    }
}
Ingvar
  • 116
  • У меня подобным образом реализовано, отдельно вынесена в класс реализация INotifyPropertyChanged, и от него наследуются классы. Но я не понимаю, где реализовать функцию подсчёта так, чтобы после изменения поля X или Y на форме сразу подставлялось значение в столбец F. Еще немного не понял момент с 'Notify()' вместо 'OnProperyChanged()', это же просто название метода... – Yarmak42 Aug 30 '23 at 13:44
  • я же сказал. В сеттере для X пиши (с учетом моего кода) Notify() - это изменения для x и сразу следом - Notify(nameof(F)) то есть при изменении Х пошлется сразу 2 сигнала, 1-й об изменении X, второй - об изменении F. И Поле в таблице обновится ридером F, Что касаемо OnProp... Как у Вас - обратите внимание на мой класс - PropertyChanged?.Invoke генерирует сообщение об изменениях если событию назначен хоть один обработчик – Ingvar Aug 30 '23 at 14:15