2
votes

I have this problems where I have all these CommandBindings defined in the MainWindow, and said commands are available within that window to be used in any Button or MenuItem. The problem is that if in other windows the command binding is not available (it's always false), even if the new Window's owner is the MainWindow.

You can see the problem here.

Any help really appreciated.

Here's the code.

XAML MainWindow:

<Window x:Class="ContextMenuDialogProblem.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ContextMenuDialogProblem"
        Title="MainWindow" Height="350" Width="525"
        FocusManager.FocusedElement="{Binding RelativeSource={x:Static RelativeSource.Self}, Mode=OneTime}">
    <Window.CommandBindings>
        <CommandBinding Command="local:LocalCommandManager.ShowDialogCommand" CanExecute="CanExecuteShowDialogCommand" Executed="ShowDialogCommandExecuted" />
    </Window.CommandBindings>
    <Window.ContextMenu>
        <ContextMenu>
            <MenuItem Command="local:LocalCommandManager.ShowDialogCommand" />
        </ContextMenu>
    </Window.ContextMenu>
    <Grid Background="Red">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Button Grid.Row="0"
                Content="Open SubWindow"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Padding="6"
                Click="Button_Click" />
        <Button Grid.Row="1"
                Content="Show Dialog Command Test"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Padding="6"
                Command="local:LocalCommandManager.ShowDialogCommand" />
    </Grid>
</Window>

CS MainWindow:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void CanExecuteShowDialogCommand(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }

    private void ShowDialogCommandExecuted(object sender, ExecutedRoutedEventArgs e)
    {
        MessageBox.Show("Show Dialog");   
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Window wnd = new SubWindow() { Owner = this };
        wnd.Show();
    }
}

CS LocalCommandManager:

public static class LocalCommandManager
{
    private static object syncRoot = new object();

    private static RoutedUICommand _showDialogCommand;
    public static RoutedUICommand ShowDialogCommand
    {
        get
        {
            lock (syncRoot)
            {
                if (_showDialogCommand == null)
                    _showDialogCommand = new RoutedUICommand("Show Dialog", "ShowDialogCommand", typeof(LocalCommandManager));
                return _showDialogCommand;
            }
        }
    }
}

XAML SubWindow:

<Window x:Class="ContextMenuDialogProblem.SubWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:local="clr-namespace:ContextMenuDialogProblem"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="SubWindow" Height="300" Width="300">
    <Grid>
        <Button Command="local:LocalCommandManager.ShowDialogCommand" Content="Show Dialog" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="6" />
    </Grid>
</Window>
3

3 Answers

3
votes

The scope of CommandBindings is limited to the element where it's defined, so this behavior is perfectly normal. You have to add the CommandBinding to SubWindow if you want to use it there.

1
votes
<StackPanel Background="Transparent">
    <StackPanel.ContextMenu>
        <ContextMenu ItemsSource="{Binding Path=AnotherWindow.CommandBindings}">
            <ContextMenu.ItemContainerStyle>
                <Style TargetType="{x:Type MenuItem}">
                    <Setter Property="Header" Value="{Binding Path=Command.Name}" />
                    <Setter Property="Command">
                        <Setter.Value>
                            <MultiBinding Converter="{StaticResource commandConverter}">
                                <Binding />
                                <Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type ContextMenu}}" />
                            </MultiBinding>


public class CommandConverter : IMultiValueConverter
{
    public object Convert(object[] value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var cb = value[0] as CommandBinding;
        var cm = value[1] as ContextMenu;

        if(cb == null || cm == null)
            return null;

        cm.CommandBindings.Add(cb);
        return cb.Command;
    }

    public object[] ConvertBack(object value, Type[] targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

work fine on .net 4.0

1
votes

If you want to add a command for all windows this is the solution:

    public partial class App : Application
    {
        public App()
        {
            var binding = new CommandBinding(MyCommands.DoSomethingCommand, DoSomething, CanDoSomething);

            // Register CommandBinding for all windows.
            CommandManager.RegisterClassCommandBinding(typeof(Window), binding);
        }

        private void DoSomething(object sender, ExecutedRoutedEventArgs e)
        {
            ...
        }

        private void CanDoSomething(object sender, CanExecuteRoutedEventArgs e)
        {
            ...
            e.CanExecute = true;
        }
    }