WPF MVVM: Команды легки. Как Соединить Представление и ViewModel с RoutedEvent

Кажется, что у zxing-js/library есть проблемы с комплектацией.

Я могу воспроизвести ошибку, запустив npm run build, а затем npm run start. Похоже, вам понадобится пакетирование, чтобы это работало в браузере. См. этот пост для получения дополнительной информации

. Тем временем вы можете использовать npm run start:dev, и вы сможете делать локальное развитие, как и ожидалось.

Спасибо @passle_ из команды @ open-wc за помощь в этом.

15
задан ArielBH 17 May 2009 в 09:16
поделиться

6 ответов

Один из вариантов - расширить рассматриваемый элемент управления и добавить поддержку конкретной команды, которая вам нужна. Например, я

8
ответ дан 30 November 2019 в 23:53
поделиться

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

Если вы привязываете выбранный элемент ListBox к свойству в ViewModel, затем, когда это свойство будет изменено, вы можете опубликовать событие. Таким образом, ViewModel остается источником события и запускается при изменении элемента, что вы и хотите.

<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />

...

public class ViewModel
{
    public IEnumerable<Item> Items { get; set; } 

    private Item selectedItem;
    public Item SelectedItem
    {
        get { return selectedItem; }
        set
        {
            if (selectedItem == value)
                return;
            selectedItem = value;
            // Publish event when the selected item changes
        }
}
43
ответ дан 30 November 2019 в 23:53
поделиться

Расширьте элемент управления для поддержки ICommandSource и решите, какое действие должно запускать команду.

Я сделал это с помощью Combo Box и использовал OnSelectionChanged в качестве триггера для команды. Сначала я покажу в XAML, как я привязываю команду к расширенному Control ComboBox, который я назвал CommandComboBox, затем я покажу код для CommandComboBox, который добавляет поддержку ICommandSource в ComboBox.

1) Использование CommandComboBox в вашем коде XAML :

В объявлениях пространства имен XAML включить

   xmlns:custom="clr-namespace:WpfCommandControlsLibrary;assembly=WpfCommandControlsLibrary">

Используйте CommandComboBox вместо ComboBox и привяжите к нему команду следующим образом: Обратите внимание, что в этом примере я определил команду с именем SetLanguageCommand в моем ViewModel, и я передаю выбранное значение для этого ComboBox в качестве параметра команды.

 <custom:CommandComboBox 
    x:Name="ux_cbSelectLanguage"
    ItemsSource="{Binding Path = ImagesAndCultures}"
    ItemTemplate="{DynamicResource LanguageComboBoxTemplate}"           
    Command="{Binding Path=SetLanguageCommand, Mode=Default}"
    CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=SelectedValue, Mode=Default}"
    IsSynchronizedWithCurrentItem="True" 
    HorizontalAlignment="Right" 
    VerticalAlignment="Center" 
    Grid.Column="1" Margin="0,0,20,0" Style="{DynamicResource GlassyComboBox}" ScrollViewer.IsDeferredScrollingEnabled="True"
 />

2) Код для CommandComboBox

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

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace WpfCommandControlsLibrary
{
   /// <summary>
   /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
   ///
   /// Step 1a) Using this custom control in a XAML file that exists in the current project.
   /// Add this XmlNamespace attribute to the root element of the markup file where it is 
   /// to be used:
   ///
   ///     xmlns:MyNamespace="clr-namespace:WpfCommandControlsLibrary"
   ///
   ///
   /// Step 1b) Using this custom control in a XAML file that exists in a different project.
   /// Add this XmlNamespace attribute to the root element of the markup file where it is 
   /// to be used:
   ///
   ///     xmlns:MyNamespace="clr-namespace:WpfCommandControlsLibrary;assembly=WpfCommandControlsLibrary"
   ///
   /// You will also need to add a project reference from the project where the XAML file lives
   /// to this project and Rebuild to avoid compilation errors:
   ///
   ///     Right click on the target project in the Solution Explorer and
   ///     "Add Reference"->"Projects"->[Select this project]
   ///
   ///
   /// Step 2)
   /// Go ahead and use your control in the XAML file.
   ///
   ///     <MyNamespace:CustomControl1/>
   ///
   /// </summary>

   public class CommandComboBox : ComboBox, ICommandSource
   {
      public CommandComboBox() : base()
      {
      }

  #region Dependency Properties
  // Make Command a dependency property so it can use databinding.
  public static readonly DependencyProperty CommandProperty =
      DependencyProperty.Register(
          "Command",
          typeof(ICommand),
          typeof(CommandComboBox),
          new PropertyMetadata((ICommand)null,
          new PropertyChangedCallback(CommandChanged)));

  public ICommand Command
  {
     get
     {
        return (ICommand)GetValue(CommandProperty);
     }
     set
     {
        SetValue(CommandProperty, value);
     }
  }

  // Make CommandTarget a dependency property so it can use databinding.
  public static readonly DependencyProperty CommandTargetProperty =
      DependencyProperty.Register(
          "CommandTarget",
          typeof(IInputElement),
          typeof(CommandComboBox),
          new PropertyMetadata((IInputElement)null));

  public IInputElement CommandTarget
  {
     get
     {
        return (IInputElement)GetValue(CommandTargetProperty);
     }
     set
     {
        SetValue(CommandTargetProperty, value);
     }
  }

  // Make CommandParameter a dependency property so it can use databinding.
  public static readonly DependencyProperty CommandParameterProperty =
      DependencyProperty.Register(
          "CommandParameter",
          typeof(object),
          typeof(CommandComboBox),
          new PropertyMetadata((object)null));

  public object CommandParameter
  {
     get
     {
        return (object)GetValue(CommandParameterProperty);
     }
     set
     {
        SetValue(CommandParameterProperty, value);
     }
  }

  #endregion

  // Command dependency property change callback.
  private static void CommandChanged(DependencyObject d,
      DependencyPropertyChangedEventArgs e)
  {
     CommandComboBox cb = (CommandComboBox)d;
     cb.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
  }

  // Add a new command to the Command Property.
  private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
  {
     // If oldCommand is not null, then we need to remove the handlers.
     if (oldCommand != null)
     {
        RemoveCommand(oldCommand, newCommand);
     }
     AddCommand(oldCommand, newCommand);
  }

  // Remove an old command from the Command Property.
  private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
  {
     EventHandler handler = CanExecuteChanged;
     oldCommand.CanExecuteChanged -= handler;
  }

  // Add the command.
  private void AddCommand(ICommand oldCommand, ICommand newCommand)
  {
     EventHandler handler = new EventHandler(CanExecuteChanged);
     canExecuteChangedHandler = handler;
     if (newCommand != null)
     {
        newCommand.CanExecuteChanged += canExecuteChangedHandler;
     }
  }
  private void CanExecuteChanged(object sender, EventArgs e)
  {

     if (this.Command != null)
     {
        RoutedCommand command = this.Command as RoutedCommand;

        // If a RoutedCommand.
        if (command != null)
        {
           if (command.CanExecute(CommandParameter, CommandTarget))
           {
              this.IsEnabled = true;
           }
           else
           {
              this.IsEnabled = false;
           }
        }
        // If a not RoutedCommand.
        else
        {
           if (Command.CanExecute(CommandParameter))
           {
              this.IsEnabled = true;
           }
           else
           {
              this.IsEnabled = false;
           }
        }
     }
  }

  // If Command is defined, selecting a combo box item will invoke the command;
  // Otherwise, combo box will behave normally.
  protected override void OnSelectionChanged(SelectionChangedEventArgs e)
  {
     base.OnSelectionChanged(e);

     if (this.Command != null)
     {
        RoutedCommand command = Command as RoutedCommand;

        if (command != null)
        {
           command.Execute(CommandParameter, CommandTarget);
        }
        else
        {
           ((ICommand)Command).Execute(CommandParameter);
        }
     }
  }

  // Keep a copy of the handler so it doesn't get garbage collected.
  private static EventHandler canExecuteChangedHandler;

  }
}
17
ответ дан 30 November 2019 в 23:53
поделиться

Отличное решение этой проблемы - использование вложенных свойств. Марлон Греч поднял использование прикрепленных свойств на новый уровень, создав Attached Command Behaviors . Используя их, можно привязать любую команду, существующую в ViewModel, к любому событию, существующему в представлении.

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

В этом примере я использую старую версию Прикрепленные командные поведения, но эффект тот же. У меня есть стиль, который используется для ListBoxItems, для которого я явно указываю. Однако было бы достаточно просто создать стиль приложения или всего окна, применяемый ко всем ListBoxItems, который устанавливает команды на гораздо более высоком уровне. Затем всякий раз, когда срабатывает событие для ListBoxItem, прикрепленного к свойству CommandBehavior.Event, оно вместо этого запускает присоединенную команду.

<!-- acb is the namespace reference to the Attached Command Behaviors -->
<Style x:Key="Local_OpenListItemCommandStyle">
    <Setter Property="acb:CommandBehavior.Event"
            Value="MouseDoubleClick" />
    <Setter Property="acb:CommandBehavior.Command"
            Value="{Binding ElementName=uiMyListBorder, Path=DataContext.OpenListItemCommand}" />
    <Setter Property="acb:CommandBehavior.CommandParameter"
            Value="{Binding}" />
</Style>

<DataTemplate x:Key="MyView">
<Border x:Name="uiMyListBorder">
<ListBox  ItemsSource="{Binding MyItems}"
          ItemContainerStyle="{StaticResource local_OpenListItemCommandStyle}" />
</Border>
</DataTemplate>
2
ответ дан 30 November 2019 в 23:53
поделиться

Well, nobody answered. So I've gave up and moved the implementation of the View outside the Dictionary into a regular UserControl, I've injected him a reference to the ViewModel.

Now when the ListBox fire the Event it's calls the ViewModel and from there everything is possible again.

Ariel

1
ответ дан 30 November 2019 в 23:53
поделиться

Try using Prism 2.

It comes with great extensions to commanding and opens many new posibilites (like commands to being tied to visual tree).

0
ответ дан 30 November 2019 в 23:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: