Добавление программно команды в список в WPF - PullRequest
1 голос
/ 30 марта 2010

В моем приложении WPF есть список с элементами. Список заполняется через xmldataprovider из XAML, а затем связывается с свойством Itemssource списка.

Ну, из XAML я связываю команду со списком, выполняя:

                      <ListBox.CommandBindings>
                          <CommandBinding 
                              Command="{x:Static local:mainApp.MyCmd}" 
                              CanExecute="CanExecute"
                              Executed ="Executed" />
                      </ListBox.CommandBindings>

но я не знаю, как программно привязать команду к каждому элементу списка. Как это сделать?

Заранее спасибо.


Сначала извините, не разместив его в качестве комментария. Я не могу поместить все это в комментарии.

Хорошо, да, я не использую свойства Executed и CanExecute ICommandSource, несмотря на то, что я зарегистрировал и реализовал их в пользовательском классе (в xaml они тоже комментированы). Я указал их в routedCommand, но не в пользовательском классе, я сделал это в конструкторе окна, выполнив следующее:

Код WinMain:

public WinMain()
{
   InitializeComponent();

   // Command binding. If I don't do this Executed and CanExecute are not executed
   CommandBindings.Add(new CommandBinding(rcmd, 
      CommandBinding_Executed, CommandBinding_CanExecute));
}

и затем я реализую эти методы в коде WinMain так же, как он:

// ExecutedRoutedEventHandler
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
   // Do stuff

}

// CanExecuteRoutedEventHandler
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{

    // cBgWorkers is a class that check if a background worker is running
    e.CanExecute = !cBgWorkers.isRunning;

    //if (LayoutListBox != null) LayoutListBox.IsEnabled = !cBgWorkers.isRunning;
}

и в WinMain XAML я вызываю команду следующим образом:

<Classes:CommandListBox x:Name="LayoutListBox"
 Command="{x:Static local:WinMain.rcmd}"
  ... >

 <...>

 </Classes:CommandListBox>

И в моем собственном классе CommandListBox у меня есть CanExecuteChanged, в котором вы можете видеть, я включил или отключил элемент управления в зависимости от того, закончен ли фоновый рабочий:

private void CanExecuteChanged(object sender, EventArgs e)
{
    this.Enabled = !cBgWorkers.isRunning;
}

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

Без реализации все идет нормально, команда вызова настраиваемого элемента управления и метод CanExecute достигнуты, и CanExecute получает правильное значение, истина или ложь в зависимости от того, завершен ли рабочий фон, или нет, а CanExecuteChanged в настраиваемом элементе управления вызывается, когда CanExecute меняет свою стоимость. Когда фоновый рабочий запускается, он отключается, но по окончании он не включается. Я отладил, и когда фоновый работник завершает работу, я вижу, что CanExecuteChanged выполняется и this.Enabled получает правильное значение (true), но по какой-то причине в пользовательском интерфейсе элемент управления продолжает отключаться, несмотря на то, что он получает правильное значение и несмотря на RunWOrkerCompleted (в фоновом режиме работник) Я заставляю обновлять пользовательский интерфейс с помощью CommandManager.InvalidateRequerySuggested ().

Я решаю это с помощью комментария:

if (LayoutListBox! = Null) LayoutListBox.IsEnabled =! CBgWorkers.isRunning;

в методе CanExecute. Я не понимаю, что происходит.

Тогда если я делаю то, что вы говорите, это не обязательно делать:

   CommandBindings.Add(new CommandBinding(rcmd, 
      CommandBinding_Executed, CommandBinding_CanExecute));

и реализации CommandBinding_Executed & CommandBinding_CanExecute. Я прав?

но если я уберу эти методы, где я могу установить this.enabled =! CBgWorkers.isRunning?

Я бы хотел, чтобы WPF автоматически устанавливал свойство isEnabled для своего пользовательского элемента управления. Как это сделать?

Заранее спасибо.


Я применяю вашу статью о прикрепленном поведении с некоторыми изменениями, чтобы адаптировать ее к моему ListBox. Это не работает хорошо или, возможно, я делаю что-то не так. Я хочу, чтобы члены ListBox (listBoxItems) могли быть выбраны, когда выполняется длинная задача (фоновый работник). Итак, один из методов статьи, которую я изменил, это:

    static void OnListBoxItemSelected(object sender, RoutedEventArgs e)
    {
        // Only react to the Selected event raised by the ListBoxItem
        // whose IsSelected property was modified.  Ignore all ancestors
        // who are merely reporting that a descendant's Selected fired.
        if (!Object.ReferenceEquals(sender, e.OriginalSource))
            return;

        ListBoxItem item = e.OriginalSource as ListBoxItem;
        if (item != null)
        {

            // (*) See comment under
            item.IsEnabled = !cBgWorkers.isRunning;
            if (!cBgWorkers.isRunning)
            {
                item.BringIntoView();
            }
        }
    }

(*) cBgWorkers - это открытый статический класс, имеющий некоторые методы и свойства. Одним из свойств является isRunning, которое указывает, что в данный момент не работают фоновые рабочие. Затем, если фоновые рабочие не запущены, элементы списка должны быть включены, в противном случае они должны быть отключены, поэтому, когда пользователь нажимает на один элемент списка, текущая страница не меняется на другой, потому что я отключил его раньше (к каждому элементу списка прикреплен один страница в моем основном приложении).

Когда работает один из фоновых рабочих (bw) или все, и я выбираю элемент списка, все в порядке: элемент списка отключен, потому что работает bw, и он не позволяет изменить текущую страницу на другую. Конечно, если я отключил элемент списка (или элементы списка), я не смогу выбрать его снова, потому что он отключен, и это моя проблема, потому что я хочу, чтобы, когда bw заканчивал элементы списка, которые были отключены во время работы bw, они снова включаются. К сожалению, с прикрепленным поведением, как я вижу, WPF не делает это автоматически, и команды имеют это преимущество (элементы управления обновляются автоматически WPF). Итак, как отключить / повторно включить элементы списка, когда bw запущен или нет соответственно?

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

Спасибо.

Ответы [ 5 ]

1 голос
/ 31 марта 2010

Я сделал ваше решение. Я сделал пользовательский элемент управления, производный от listbox и реализующий ISourceCommand, как вы сказали, и теперь он работает !!!! ;)

Мой пользовательский класс:

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

namespace GParts.Classes
{
public class CommandListBox : ListBox, ICommandSource
{
    public CommandListBox() : base()
    {

    }

    // ICommand Interface Members
    // Make Command a dependency property so it can use databinding.
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register(
            "Command",
            typeof(ICommand),
            typeof(CommandListBox),
            new PropertyMetadata((ICommand)null,
            new PropertyChangedCallback(CommandChanged)));

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

    // Make Command a dependency property so it can use databinding.
    public static readonly DependencyProperty ExecutedProperty =
        DependencyProperty.Register(
            "Executed",
            typeof(object),
            typeof(CommandListBox),
            new PropertyMetadata((object)null));

    public object Executed
    {
        get
        {
            return (object)GetValue(ExecutedProperty);
        }
        set
        {
            SetValue(ExecutedProperty, value);
        }
    }

    // Make Command a dependency property so it can use databinding.
    public static readonly DependencyProperty CanExecuteProperty =
        DependencyProperty.Register(
            "CanExecute",
            typeof(object),
            typeof(CommandListBox),
            new PropertyMetadata((object)null));

    public object CanExecute
    {
        get
        {
            return (object)GetValue(CanExecuteProperty);
        }
        set
        {
            SetValue(CanExecuteProperty, value);
        }
    }

    // Make CommandTarget a dependency property so it can use databinding.
    public static readonly DependencyProperty CommandTargetProperty =
        DependencyProperty.Register(
            "CommandTarget",
            typeof(IInputElement),
            typeof(CommandListBox),
            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(CommandListBox),
            new PropertyMetadata((object)null));

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

    // Command dependency property change callback.
    private static void CommandChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        CommandListBox clb = (CommandListBox)d;
        clb.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;

        //newCommand.Execute(null);
        //newCommand.CanExecute(null);

    }

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

            //newCommand.Execute(Executed);
            //newCommand.CanExecute(CanExecute);
        }
    }
    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;
                }
            }
        }
    }

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

и в моем WinMain.xaml:

    <Classes:CommandListBox x:Name="LayoutListBox"
     Command="{x:Static local:WinMain.rcmd}"

     <!-- These lines doesn't work I explain it following
     Executed="CommandBinding_Executed"
     CanExecute="CommandBinding_CanExecute" 
     -->

      ... >

     <...>

     </Classes:CommandListBox>

и код окна позади:

    public WinMain()
    {
       InitializeComponent();

       // Command binding. If I don't do this Executed and CanExecute are not executed
       CommandBindings.Add(new CommandBinding(rcmd, 
          CommandBinding_Executed, CommandBinding_CanExecute));
    }

    public static RoutedCommand rcmd = new RoutedCommand();

    // ExecutedRoutedEventHandler
    private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
    {
       // Do stuff

    }

    // CanExecuteRoutedEventHandler
    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {

        e.CanExecute = !cBgWorkers.isRunning;

        //if (LayoutListBox != null) LayoutListBox.IsEnabled = !cBgWorkers.isRunning;
    }

но у меня та же проблема, что и у другого решения. Если я не помещу последнюю строку (здесь появляется комментарий в CommandBinding_CanExecute), список не включается автоматически с помощью wpf, когда фоновый работник завершает работу. Если я поставлю эту строку, это работает. Что происходит?

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

Большое спасибо.

1 голос
/ 31 марта 2010

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

0 голосов
/ 31 марта 2010

Я не смог весь поток. Это довольно долго. В любом случае, я думал, что вы хотите поместить команду в ListBoxItem? Из того, что я вижу, вы унаследовали от ListBox. Вам не нужно указывать свойства Executed и CanExecute для ICommandSource. Это должно быть указано в вашем RoutedCommand, а не в вашем пользовательском элементе управления. Чтобы ваша команда была выполнена, вам нужно предоставить обработчик событий в вашем пользовательском элементе управления. Например, если выбран элемент, вы выполняете команду. Вот пример.

protected override void OnSelected(RoutedEventArgs e)   
{
    base.OnSelected(e);

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

        if (command != null)
        {
            command.Execute(CommandParameter, CommandTarget);
        }
        else
        {
            ((ICommand)Command).Execute(CommandParameter);
        }
    }
}
0 голосов
/ 31 марта 2010

Согласно первому вопросу, который я написал, использование CommandBindings в списке не работает. Реализация CanExecute была:

    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {

        e.CanExecute = !cBgWorkers.isRunning;         

    }

При выполнении WPF не включается / не отключается элемент управления списком автоматически в зависимости от фонового рабочего состояния (работает или нет), и я не понимаю почему, потому что у меня есть другие элементы управления, такие как кнопки со связанными командами, а WPF автоматически включает / отключает им.

Итак, я сделал следующую модификацию:

    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {

        e.CanExecute = !cBgWorkers.isRunning;

        if (LayoutListBox != null) LayoutListBox.IsEnabled = !cBgWorkers.isRunning;
    }

Теперь это работает. Listbox включается, когда фоновый рабочий не запущен и отключен, иначе мне не нравится последняя строка в методе, в которой я включаю / отключаю вручную свойство isEnabled списка. Это неэффективно, поэтому я хотел бы изменить свойство isEnabled списка только тогда, когда CanExecute меняет свое значение. Насколько я знаю, для этого есть событие, это CanExecuteChanged, но я не знаю, как его реализовать. Есть идеи?

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

0 голосов
/ 31 марта 2010
...