WPF DataGrid - уведомить другой класс о выбранном пользователем значении - PullRequest
0 голосов
/ 04 ноября 2018

Я пробовал это в течение нескольких дней. Я новичок в кодировании и совершенно новый для WPF и DataGrids. Любая помощь с благодарностью.

См. Изображение таблицы данных

По существу, сетка данных слева имеет другой ItemSource, чем тот, что справа.

В правой таблице данных пользователь выбирает марку, для которой он хочет рассчитать общую сумму. Я фиксирую выбранную строку и получаю оценку бренда следующим образом -

        private void BrandGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
    {
        List<DataGridCellInfo> info = BrandGrid.SelectedCells.ToList();
        rate = 0;
        if (info.Count > 0)
        {
            Brand i = (Brand)info[1].Item;
            rate = i.Rate;
        }
    }

Теперь, как мне сказать другому классу использовать выбранную ставку для расчета общей суммы. Таким же образом, как я рассчитываю общую площадь

TotalArea = длина * количество * someConstantWidth (это было легко с помощью INotifyPropertyChanged). Когда пользователь изменяет длину и количество в сетке данных, TotalArea будет обновляться одновременно.

Принимая во внимание, что здесь TotalAmount = TotalArea * SelectedRate , я не совсем понимаю, как отправлять информацию из MainWindow.BrandGrid_SelectedCellsChanged в другой класс.

Я попытался возиться с делегатом и событиями безуспешно, потому что в моем примере приложения мне сложно его реализовать.

Какой метод самый лучший? Как мне продолжать это?

UPDATE
Большое вам спасибо за такой подробный ответ! У меня есть еще несколько сомнений относительно реализации,

(1) На шаге 1 строка кода: public void SelectBrand () => OnBrandSelect? .Invoke (this, Rate); Я предполагаю, что здесь Rate это имя переменной, а не ее тип? Моя переменная Rate имеет двойной тип.

(2) Чтобы обратиться к вашей заметке о классе результатов: длина и количество являются пользовательскими данными, с помощью которых вычисляется общая площадь. Здесь длина, количество, площадь и т. Д. Находятся в пределах одного и того же класса Result, и я использую INotifyPropertyChanged для обновления столбца TotalArea.

(3) В вашем примере для Result Class требуется, чтобы я создал ResultObject с вводом типа бренда. Любым другим путем? Поскольку пользователь должен иметь возможность указать всю длину и количество заказа, размещенного клиентом, а затем выбрать марку позже.

Мне бы хотелось, чтобы вывод был похож на то, как INotifyPropertyChanged обрабатывает изменения. Он изменяет ячейки в реальном времени, так как я изменяю входные данные в других ячейках - подобное поведение, если возможно, было бы потрясающим.

ОБНОВЛЕНИЕ 2
Б. Спангенберг, вы дали отличное решение. Я попробовал ваш код и добавил кнопку для добавления элементов, и код работает отлично, но я думаю, что в моем вопросе не хватает некоторых требований.

DataGrid, который у меня есть, является «IsEnabled» и позволяет добавлять новую строку - после добавления текущей строки автоматически появляется новая строка, которую можно редактировать.

Принимая во внимание ваше решение, вот правильные требования -
(1) Нет кнопки для добавления элементов в OrderGrid. Новая строка появляется автоматически.

(2) Марку можно выбрать сначала до ввода предметов или после ввода предметов.

(3) Как только пользователь выбирает марку, ВСЕ элементы обновляются. НЕТ выбора элемента в OrderGrid.

(4) Если пользователь добавляет новый элемент в OrderGrid после выбора бренда, TotalAmount нового элемента рассчитывается на основе бренда, который уже выбран в настоящее время.

Примечание: Вы правы насчет "SelectedCellsChanged". Я изменил это на событие MouseDoubleClick. Теперь это очень надежно, а также удобнее для пользователя. СПАСИБО!

Ответы [ 2 ]

0 голосов
/ 11 ноября 2018

Написание этого для тех, кто может ожидать другого поведения с INotifyPropertyChanged.

Я заметил, что изменение значения в реальном времени не может быть вызвано с помощью -

public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
   PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

В режиме реального времени Я имею в виду - когда вы меняете значение в сетке данных, другое значение в другом столбце, одна и та же строка изменяется одновременно. Принимая во внимание, что в приведенном выше коде вам нужно щелкнуть по ячейке для значения, чтобы обновить.

Лучший способ для этого аккуратного поведения -

public void NotifyPropertyChanged(string property)
{
   PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

И вызвать этот метод как NotifyPropertyChanged (yourVariableName);

0 голосов
/ 06 ноября 2018

Итак, я предполагаю, что вы пытаетесь вызвать событие от выбранного вами бренда, чтобы сообщить кому-либо, что рейтинг бренда равен (), который затем используется для расчета итогового значения.

Шаг 1: Вам нужно добавить событие в свой класс «Бренд» и создать открытый метод для его внутреннего вызова.

class Brand
{
    public string Name { get; set; }
    public Rate Rate { get; set; }

    public void SelectBrand() => OnBrandSelect?.Invoke(this, Rate);

    public event EventHandler<Rate> OnBrandSelect;
}

Шаг 2: Подключайте события своего бренда, когда вы загружаете их в коллекцию, которая содержит их, которые используются в качестве источника сетки. Базовый пример ниже.

// load brands from db or elswhere.
List<Brand> brandsSelectedToLoad = new List<Brand>()

 ObservableCollection<Brand> BrandDisplayCollectionToBeUsedAsGridSource = new ObservableCollection<Brand>();
 foreach (Brand brand in brandsSelectedToLoad)
 {
      brand.OnBrandSelect += (s, args) => { /* Call calculation method (TotalAmount = TotalArea * args -- args is rate) and set the result */}
      BrandDisplayCollectionToBeUsedAsGridSource.Add(brand);
 }

Шаг 3: Затем, когда вы выбираете свой бренд из сетки, вы вызываете событие бренда. Любые слушатели должны затем выполнять свою работу.

private void BrandGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
     List<DataGridCellInfo> info = BrandGrid.SelectedCells.ToList();
     if (info.Count > 0)
     {
         Brand selectedBrand = info[1].Item as Brand;
         if (selectedBrand == null)
             return;

         selectedBrand.SelectBrand();
     }
}

ПРИМЕЧАНИЕ: Другая сетка, которую я буду называть «Сетка результатов», скорее всего, должна будет отражать эти расчеты на основе скорости, вызванной событием, в столбце Общая сумма. Я предполагаю, что эти элементы в вашей "таблице результатов" являются фактическими объектами результата, но я не вижу четкой связи между записью результата и выбранный бренд. (если только он не спрятан в объекте результата). Который заставляет меня поверить, что вы просто добавляете запись в таблицу результатов при выборе бренда и забываете ее.

Вам нужно будет встроить ту же ссылку на объект Brand, которую вы загрузили в свою таблицу Brands Grid, в свой объект результата и перехватить его событие внутри объекта результата. (Почти так же, как на втором шаге). Тогда вы можете проигнорировать второй шаг и использовать ниже.

Пример:

class Result : INotifyPropertyChanged
{
    private double total;

    public Result(Brand brandRef)
    {         
        this.Brand = brandRef;
        brand.OnBrandSelect += (s, args) => 
            { 
               /* Call calculation method (TotalAmount = TotalArea * 
                  args -- args is rate) and set the result */

               Total = /*(TotalAmount = TotalArea * 
                  args -- args is rate)*/;
            }
    }

    public double Total   
    {
        get { return total; }
        set
        {
            total = value;
            NotifyPropertyChanged();
        }
    }

    public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Обновление ответа

Объект оценки может быть заменен вашим двойным типом. Просто поменяйте местами все «ставки» с двойными.

Понятно. Таким образом, выбор бренда доступен только после того, как пользователь настроил ввод длины и количества. Поэтому использование параметра brand в конструкции невозможно, поскольку вам необходимо заранее создать экземпляр объекта результата.

Выполните следующие действия:

1) Создайте объект бренда, как он есть на первом этапе. Просто замените тариф на двойной.

2) Добавьте метод "Calculate (double rate)" к вашему результирующему объекту, который принимает double. В рамках этого метода вы запускаете вычисления для итогов и задаете свойство итогов.

public void Caculate(double rate)
{
    TotalAmount = TotalArea * rate;
}

3) Итак, теперь вам нужен какой-то способ, чтобы держать ваше событие подписанным на выбранную строку сетки в таблице результатов. Поэтому каждый раз, когда вы добавляете объект результата в свою исходную коллекцию сетки, вам нужно будет привязывать к нему каждое событие объекта бренда.

Так что я предполагаю, что где-то у вас есть кнопка, которая добавляет новую запись в таблицу результатов, которая будет настроена пользователем, а затем он выбирает марку. Непосредственно перед точкой, где вы добавляете объект в результирующий объект. Крюк со следующим.

ResultObject resultObj = new ResultObject()
BrandCollection.All(brand => 
{ 
   brand.OnBrandSelect += (s, rate) => 
   { 
      resultObj.Calculate(rate);
   }; return true; 
});
resultGridCollection.Add(resultObj);

Так что теперь у каждого объекта бренда есть подключаемая функция, которая вычисляет и устанавливает существующий объект результата при выборе. Вы по-прежнему используете Шаг 3, чтобы вызвать событие выбора.

Имейте в виду, что вам нужно будет отписываться от всех событий бренда каждый раз, когда вы добавляете новый объект, иначе выбор бренда будет продолжать изменять все ваши результаты. Если вы хотите поддержать изменения для выбранного GridResult, вам просто нужно переместить код подписки из метода add в метод выбранной ячейки сетки для результата.

Надеюсь, все это имеет смысл.

Обновление 2

Я решил пройти лишнюю милю для вас. Вот код, который вам нужен. Все это.

namespace GridSelection
{
   /// <summary>
   /// Interaction logic for MainWindow.xaml
   /// </summary>
    public partial class MainWindow : Window
    {
        MainWindowViewModel model = new MainWindowViewModel();
        public MainWindow()
        {
           InitializeComponent();
           OrdersGrid.SelectedCellsChanged += OrdersGrid_SelectedCellsChanged;
           BrandGrid.SelectedCellsChanged += BrandGrid_SelectedCellsChanged;
           this.Loaded += MainWindow_Loaded;
     }


    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        model.Load();
        this.DataContext = model;
    }

    private void OrdersGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
    {
        DataGrid grid = sender as DataGrid;
        Order selectedOrder = grid.SelectedItem as Order;

        if (selectedOrder == null)
            return;

        model.BrandCollection.All(b => 
        {
            b.UnsubscribeAll();
            b.OnBrandSelect += (s, rate) => 
            {
                selectedOrder.CalculateTotal(rate);
            }; return true;
        });
    }

    private void BrandGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
    {
        DataGrid grid = sender as DataGrid;
        Brand selectedbrand = grid.SelectedItem as Brand;

        if (selectedbrand == null)
            return;

        selectedbrand.InvokeBrandSelected();
    }
}

internal class MainWindowViewModel
{
    public ObservableCollection<Brand> BrandCollection { get; private set; }
    public ObservableCollection<Order> OrderCollection { get; private set; }
    public ICommand AddNewOrderCommand { get; private set; }

    public void Load()
    {
        BrandCollection = new ObservableCollection<Brand>
        {
            new Brand() { Name = "Brand One", Rate = 20 },
            new Brand() { Name = "Brand Two", Rate = 30 },
            new Brand() { Name = "Brand Three", Rate = 50 }
        };

          OrderCollection = new ObservableCollection<Order>();

          AddNewOrderCommand = new CustomCommand(p => 
          {
              OrderCollection.Add(new Order());
          });
      }
  }

  public class Order : INotifyPropertyChanged
  {
    #region Private Variables
      private int length;
      private int quantity;
      private int totalArea;
      private double totalAmount;
     #endregion

      #region Public Properties
      public int Length
      {
           get { return length; }
           set
           {
               length = value;
               NotifyPropertyChanged();

               CalculateArea();
           }
       }

        public int Quantity
        {
            get { return quantity; }
            set
            {
                quantity = value;
                NotifyPropertyChanged();

                CalculateArea();
            }
        }

        public int TotalArea
        {
            get { return totalArea; }
            set
            {
                totalArea = value;
                NotifyPropertyChanged();
            }
        }

        public double TotalAmount
        {
            get { return totalAmount; }
            set
           {
               totalAmount = value;
               NotifyPropertyChanged();
           }
        }

        #endregion

        private void CalculateArea()
        {
            TotalArea = this.Length * this.Quantity;
        }
        public void CalculateTotal(double rate)
        {
            TotalAmount = this.TotalArea * rate;
        }

        #region Public Methods
        public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        #region Events
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion
    }

    public class Brand
    {
        public string Name { get; set; }
        public double Rate { get; set; }

        public void InvokeBrandSelected() => OnBrandSelect?.Invoke(this, Rate);
        public void UnsubscribeAll() => OnBrandSelect = null;

        public event EventHandler<double> OnBrandSelect;
    }

    // Interface 
    public interface ICustomCommand : ICommand
    {
        event EventHandler<object> Executed;
    }

    // Command Class
    public class CustomCommand : ICustomCommand
    {
        #region Private Fields
        private readonly Action<object> _execute;
        private readonly Func<object, bool> _canExecute;
        #endregion

        #region Constructor
        public CustomCommand(Action<object> execute) : this(execute, null)
        {
        }

        public CustomCommand(Action<object> execute, Func<object, bool> canExecute)
        {
            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute ?? (x => true);
        }
        #endregion

        #region Public Methods
        public bool CanExecute(object parameter)
        {
            return _canExecute(parameter);
        }

        public void Execute(object parameter = null)
        {
            Refresh();
            _execute(parameter);
            Executed?.Invoke(this, parameter);
            Refresh();
        }

        public void Refresh()
        {
            CommandManager.InvalidateRequerySuggested();
        }
        #endregion

        #region Events
        public event EventHandler CanExecuteChanged
        {
            add
            {
                CommandManager.RequerySuggested += value;
            }
            remove
            {
                CommandManager.RequerySuggested -= value;
            }
        }
        public event EventHandler<object> Executed;
        #endregion
    }
}

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

UPDATE

Вот код, основанный на размещенном вами обновлении.

namespace GridSelection
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
   public partial class MainWindow : Window
   {
       MainWindowViewModel model = new MainWindowViewModel();
       public MainWindow()
       {
           InitializeComponent();

           BrandGrid.MouseDoubleClick += BrandGrid_MouseDown;
           OrdersGrid.InitializingNewItem += OrdersGrid_InitializingNewItem; ;

           this.Loaded += MainWindow_Loaded;
       }

    private void OrdersGrid_InitializingNewItem(object sender, InitializingNewItemEventArgs e)
    {
        Order newOrder = e.NewItem as Order;
        if (newOrder == null)
            return;

        if(model.CurrentBrand != null)
        {
            newOrder.UpdateRate(model.CurrentBrand.Rate);
        }

        model.BrandCollection.All(b =>
        {
            b.OnBrandSelect += (s, rate) =>
            {
                newOrder.UpdateRate(model.CurrentBrand.Rate);
                newOrder.CalculateTotal();
            }; return true;
        });
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        model.Load();
        this.DataContext = model;
    }

    private void BrandGrid_MouseDown(object sender, MouseButtonEventArgs e)
    {
        DataGrid grid = sender as DataGrid;
        Brand selectedbrand = grid.SelectedItem as Brand;

        if (selectedbrand == null)
            return;

        selectedbrand.InvokeBrandSelected();
    }
}

   internal class MainWindowViewModel : INotifyPropertyChanged
   {
       private Brand currentBrand;

       public ObservableCollection<Brand> BrandCollection { get; private set; }
       public ObservableCollection<Order> OrderCollection { get; private set; }

       public Brand CurrentBrand
       {
           get { return currentBrand; }
           set
           {
               currentBrand = value;
               NotifyPropertyChanged();
           }
       }

       public void Load()
       {
           BrandCollection = new ObservableCollection<Brand>
           {
              new Brand() { Name = "Brand One", Rate = 20 },
              new Brand() { Name = "Brand Two", Rate = 30 },
              new Brand() { Name = "Brand Three", Rate = 50 }
           };

           OrderCollection = new ObservableCollection<Order>();
       }

       public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
       {
           PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
       }

       public event PropertyChangedEventHandler PropertyChanged;
   }

   public class Order : INotifyPropertyChanged
   {
      #region Private Variables
      private int length;
      private int quantity;
      private int totalArea;
      private double totalAmount;
      #endregion

      public Order()
      {

      }

       #region Properties
       private double Rate { get; private set; }
       public int Length
       {
         get { return length; }
         set
          {
               length = value;
               NotifyPropertyChanged();

               CalculateArea();
               CalculateTotal();
           }
       }
       public int Quantity
       {
           get { return quantity; }
           set
           {
               quantity = value;
               NotifyPropertyChanged();

               CalculateArea();
               CalculateTotal();
           }
       }
       public int TotalArea
       {
           get { return totalArea; }
           set
           {
               totalArea = value;
               NotifyPropertyChanged();
           }
       }
       public double TotalAmount
       {
           get { return totalAmount; }
           set
           {
               totalAmount = value;
               NotifyPropertyChanged();
           }
       }
       #endregion

       #region Methods
       private void CalculateArea()
       {
           TotalArea = this.Length * this.Quantity;
       }

       public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
       {
           PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
       }
       public void CalculateTotal()
       {
           TotalAmount = this.TotalArea * this.Rate;
       }
       public void UpdateRate(double rate)
       {
           Rate = rate;
       }
       #endregion

       #region Events
       public event PropertyChangedEventHandler PropertyChanged;
       #endregion
   }

   public class Brand
   {
       public string Name { get; set; }
       public double Rate { get; set; }

       public void InvokeBrandSelected() => OnBrandSelect?.Invoke(this, Rate);
       public void UnsubscribeAll() => OnBrandSelect = null;

       public event EventHandler<double> OnBrandSelect;
   }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...