以编程方式将命令添加到WPF中的列表框

时间:2010-03-30 15:03:00

标签: wpf command listboxitem

在我的WPF应用程序中,有一个包含项目的列表框。列表框通过XAML中的xmldataprovider填充,然后将其绑定到列表框的Itemssource属性。

好吧,从XAML,我通过执行以下命令将命令绑定到列表框:

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

但我不知道如何以编程方式将命令绑定到每个listboxitem。怎么做?

提前致谢。


首先抱歉不发表评论。我不能把所有这些都放在评论中。

好的,是的,我没有使用ICommandSource的Executed和CanExecute属性,尽管我已经在自定义类中注册并实现了它们(在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获取正确的值,true或false取决于后台worker是否完成,并且CanExecute时引发自定义控件中的CanExecuteChanged改变它的价值。当后台工作程序启动时,它会被禁用,但是当它完成时它不会启用。我已经调试了,当后台工作程序完成时我可以看到CanExecuteChanged被执行并且this.Enabled正在获得正确的值(true)但由于某种原因在UI中控件继续被禁用,尽管它获得了正确的值,尽管在RunWOrkerCompleted中(在后台) worker)我强制用CommandManager.InvalidateRequerySuggested()更新UI。

我通过取消注释行来解决这个问题:

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

CanExecute方法中的

。我不明白会发生什么。

然后,如果我按照你说的做,没有必要这样做:

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

和CommandBinding_Executed&amp; 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,表示当前没有后台工作程序正在运行。然后,如果没有后台工作程序正在运行,则必须启用列表框成员,否则必须禁用它们,因此当用户单击一个列表框项目时,当前页面不会更改为另一个,因为我之前禁用了它(每个listBox项目附加了一个我主应用程序中的页面)。

当其中一个后台工作人员(bw)或全部正在运行并且我选择列表框项时,一切正常:列表框项被禁用,因为有bw正在运行并且它避免将当前页面更改为另一个页面。当然,如果我禁用了列表框项目(或列表框项目),我无法再次选择它,因为它已被禁用,这是我的问题,因为我想要当bw完成在bw运行时已禁用的列表框项目时,他们再次启用。不幸的是,附加行为,因为我看到它不是由WPF自动完成的,并且命令具有此优势(控件由WPF自动更新)。那么,如何在bw分别运行时禁用/重新启用列表框项目?

据我所知,附加行为的一个优点是我认为它更有效率,因为它们不会不断地调用动作(只有当动作,例如,选择产生时)。命令经常(不经常)检查是否可以执行绑定到控件的操作(因此,如果它们可以执行,WPF会自动启用控件,否则它们会被禁用),对吗?

感谢。

5 个答案:

答案 0 :(得分:1)

您可以尝试创建一个从ListBoxItem派生并实现ICommandSource接口的自定义控件。到目前为止,我想不出更简单的解决方案。

答案 1 :(得分:1)

我已经完成了你的解决方案。我已经完成了一个自定义用户控件,从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自动启用。如果我把它放在这条线上就行了。会发生什么事?

另一件事,正如你在我的代码片段中看到的那样,我想做的就像我用一个按钮一样,你可以指示命令,执行和canexecute。我已经在课堂上注册了它们,在列表框中我检查了传递方法,但它没有用。我怎么能这样做?

非常感谢。

答案 2 :(得分:0)

答案 3 :(得分:0)

根据我发布的第一个问题,在列表框中使用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;
    }

现在,它有效。当没有后台工作程序正在运行和禁用时启用列表框,否则我不喜欢的是放入方法的最后一行,我手动启用/禁用列表框的属性isEnabled。这是低效的,所以我只想在CanExecute更改其值时更改列表框的isEnabled属性。据我所知有一个事件要做,这是CanExecuteChanged但我不知道如何实现它。有什么想法吗?

现在,在尝试了几个解决方案后,我正在实施迈克的解决方案,因为我认为它更容易,更清晰,并且只需进行一些更改就可以重新用于其他控件。

答案 4 :(得分:0)

我没能完成整个帖子。这很长。无论如何,我以为你想在ListBoxItem上放一个命令?从我看到的,你继承自ListBox。您无需指定ICommandSource的Executed和CanExecute属性。这应该在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);
        }
    }
}