-1

Revised: I apologize for missing some important descriptions in the first version, now the problem should be well-defined:

so I'm making a toy CAD program with following views:

  1. MainWindow.xaml
  2. CustomizedUserControl.xaml

CustomizedUserControl is a Tab within MainWindow, and its DataContext is defined in MainWindow.xaml as:

 <Window.Resources>
        <DataTemplate DataType="{x:Type local:CustomizedTabClass}">
            <local:UserControl1/>
        </DataTemplate>
 </Window.Resources>

And CustomizedUserControl.xaml provides a canvas and a button, so when the button is pressed the user should be able to draw on the canvas. As the following code shows, the content of Canvas is prepared by the dataContext, "tabs:CustomizedTabClass".

CustomizedUserControl.xaml


<CustomizedUserControl x:Name="Views.CustomizedUserControl11"
...
>
<Button ToolTip="Lines (L)" BorderThickness="2"
                        Command="{Binding ElementName=CustomizedUserControl11, 
                        Path=DrawingCommands.LinesChainCommand}"
                        IsEnabled="True"
                        Content = "{Binding ElementName=CustomizedUserControl11, 
                        Path=DrawingCommands.Button1Name}">
</Button>
...
<canvas x:Name="CADCanvas"
        Drawing="{Binding Drawing ,Mode=TwoWay}" >
</canvas>

It is also notable that I used an external library, Fody/PropertyChanged, in all classes so property notifications would be injected without further programming.

CustomizedUserControl.xaml.cs

using PropertyChanged;
using System.ComponentModel;
using System.Windows.Controls;

[AddINotifyPropertyChangedInterface]
public partial class CustomizedUserControl: Usercontrol, INotifyPropertyChanged{
  public CADDrawingCommands DrawingCommands { get; set; }
  public CustomizedUserControl()
  {
      InitializeComponent();
      DrawingCommands = new CADDrawingCommands(this);
      DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
  }
  public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };

}

CADDrawingCommands.cs

using PropertyChanged;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows;

[AddINotifyPropertyChangedInterface]
public class CADDrawingCommands : INotifyPropertyChanged{
  UserControl _drawableTab;
  public string Button1Name { get; set; } = "TestForDataBinding";
  public RoutedCommand LinesChainCommand { get; set; } = new RoutedCommand();
  public CADDrawingCommands(UserControl dTab){
        _drawableTab = dTab;
        CommandBinding lineCommandBinding = new CommandBinding(LinesChainCommand,
             (object sender, ExecutedRoutedEventArgs e) =>
             {
                 MessageBox.Show("Test");
                 //Draw on canvas inside CustomizedUserControl (modify Drawing property in CustomizedTabClass)
             }, (object sender, CanExecuteRoutedEventArgs e) => { e.CanExecute = true; });

            _drawableTab.CommandBindings.Add(lineCommandBinding);       

        }
 public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };

}

The Content of Button is set correctly, as I can read the string defined in Button1Name:

enter image description here

Therefore I suppose the Data Binding for Command is also ok. IsEnabled has been set to true and CanExecute of the CommandBinding would only return true.

Why is my button still greyed out and not clickable?

If I define the button inside a Window instead of UserControl (and set the datacontext of the Window to its own code behind, the button will be clickable! Why?

Thank you for your time! Hopefully would somebody help me cuz I've run out of ideas and references.

Hank
  • 23
  • 1
  • 6
  • 1
    You add the CommanBinding to the collection of the `_drawableTab` element. But it is not clear from your code what this element is. – EldHasp May 01 '21 at 18:34
  • _drawable is the CustomizedUserControl , because I want to set the CommandBindings property of my UserControl, so that when the Button in UserControl is pressed, the user can draw line on the canvas located also in that UserControl. – Hank May 01 '21 at 21:29

2 Answers2

1

If you showed your code in full, then I see the following problems in it:

  1. You are setting the value incorrectly for the DrawingCommands property. In this property, you do not raise PropertyChanged. The binding in the Button is initialized in the InitializeComponent() method. At this point, the property is empty, and when you set a value to it, the binding cannot find out.

There are two ways to fix this:

  • Raise PropertyChanged in the property;
  • If you set the property value once in the constructor, then set it immediately in the initializer. Make the property "Read Only". This way, in my opinion, is better.
  public CADDrawingCommands DrawingCommands { get; }
  public FileEditTabUserControl()
  {
      DrawingCommands = new CADDrawingCommands(this);

      InitializeComponent();
      DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
  }

  1. You have a button bound to a command in the DrawingCommands.LinesChainCommand property. But to this property, you assign an empty instance of the = new RoutedCommand () routing command. This looks pointless enough. If you need a routable command, create it in the "Read Only" static property. This will make it much easier to use in XAML:
    public static RoutedCommand LinesChainCommand { get; }  = new RoutedCommand();
<Button ToolTip="Lines (L)" BorderThickness="2"
                        Command="{x:Static local:DrawingCommands.LinesChainCommand}"
                        IsEnabled="True"
                        Content = "{Binding ElementName=CustomizedUserControl11, 
                        Path=DrawingCommands.Button1Name}">
</Button>
  1. Raising PropertyChanged in CADDrawingCommands properties is also not visible in your code. If it really does not exist, then the binding is also unaware of changing property values.
EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • Thank you for your detailed answer. I would like to apologize for the unclear information given in my question. 1. As you can see in my updated question, an external library Fody/PropertyChanged is used for every classes you mentioned, so that property notification are injected without further programming. I think PropertyChanged is well set, evidence is that I changed a property "Button1Name" of the CADDrawingCommands, and the change is applied to my button, which means change notifications are raised. – Hank May 01 '21 at 21:39
  • 2. As for your suggestion to make RoutedCommand static, I would try working on that! Any further Ideas are appreciated!!! – Hank May 01 '21 at 21:43
  • 1
    Using **Fody** removes all questions regarding raising **PropertyChanged**. – EldHasp May 02 '21 at 07:22
  • Yes! But I couldn't enable the button with IsEnabled==true and the binding Command returning true for CanExecute. I wonder what are the factors possibly behind this. Does it have something to do with logical focus of the Button? Because if I define the button in a Window it would work, but in UserControl it would be always disabled. – Hank May 02 '21 at 07:32
  • 1
    Using a static RoutedCommand instance is the typical WPF way. All default routable commands in WPF are also implemented as a unique static instance: ApplicationCommands, EditingCommands, ComponentCommands, MediaCommands, NavigationCommands. Also, where commands are needed in the internal logic of UI controls, they are also implemented as static instances. Example: in the ScrollBar class there are almost two dozen of their static RoutedCommands. – EldHasp May 02 '21 at 07:34
  • 1
    I don't see any obvious problems in the code you showed. I will try to repeat your code and reproduce the problem. I will inform you about the result. – EldHasp May 02 '21 at 07:37
  • 1
    Tried to reproduce your example and came across one ambiguity. You declare `class CustomizedUserControl`, but in the constructor you have a different name `public FileEditTabUserControl()`. Please explain this ambiguity. – EldHasp May 02 '21 at 08:03
  • just updated the codes :) It should be CustomizedUserControl – Hank May 02 '21 at 08:05
1

Made the simplest example.
Everything works as it should.
BaseInpc is my simple INotifyPropertyChanged implementation from here: BaseInpc

using Simplified;
using System.Windows;
using System.Windows.Input;

namespace CustomizedUserControlRoutedCommand
{
    public class CADDrawingCommands : BaseInpc
    {
        UIElement _drawableTab;
        private string _button1Name = "TestForDataBinding";

        public string Button1Name { get => _button1Name; set => Set(ref _button1Name, value); }
        public static RoutedCommand LinesChainCommand { get; } = new RoutedCommand();
        public CADDrawingCommands(UIElement dTab)
        {
            _drawableTab = dTab;
            CommandBinding lineCommandBinding = new CommandBinding(LinesChainCommand,
                 (object sender, ExecutedRoutedEventArgs e) =>
                 {
                     MessageBox.Show("Test");
                     //Draw on canvas inside CustomizedUserControl (modify Drawing property in CustomizedTabClass)
                 }, (object sender, CanExecuteRoutedEventArgs e) => { e.CanExecute = true; });

            _drawableTab.CommandBindings.Add(lineCommandBinding);
        }
    }
}
<UserControl x:Name="CustomizedUserControl11" x:Class="CustomizedUserControlRoutedCommand.CustomizedUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:CustomizedUserControlRoutedCommand"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Button ToolTip="Lines (L)" BorderThickness="2"
                        Command="{x:Static local:CADDrawingCommands.LinesChainCommand}"
                        IsEnabled="True"
                        Content = "{Binding ElementName=CustomizedUserControl11, 
                        Path=DrawingCommands.Button1Name}">
        </Button>
    </Grid>
</UserControl>
using System.Windows.Controls;

namespace CustomizedUserControlRoutedCommand
{
    public partial class CustomizedUserControl : UserControl
    {
        public CADDrawingCommands DrawingCommands { get; }
        public CustomizedUserControl()
        {
            DrawingCommands = new CADDrawingCommands(this);

            InitializeComponent();
            DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
        }
    }
}

<Window x:Class="CustomizedUserControlRoutedCommand.TestCustomizedUserControlWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CustomizedUserControlRoutedCommand"
        mc:Ignorable="d"
        Title="TestCustomizedUserControlWindow" Height="450" Width="800">
    <Grid>
        <local:CustomizedUserControl/>
    </Grid>
</Window>
EldHasp
  • 6,079
  • 2
  • 9
  • 24