S
S
Space Purr2019-04-04 19:30:00
WPF
Space Purr, 2019-04-04 19:30:00

How to bind the Command Parameter property of the Context Menu to the Name property of the TextBox to which this menu is attached?

Hello.
So I'll start with what I did, and then smoothly move on to what I did not succeed.
I have 6 TextBoxes which have the same ContextMenu.

<TextBox Grid.Column="2" Grid.Row="5" Text="{Binding TextBoxes[110].BoxValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                    <TextBox.ContextMenu>
                        <ContextMenu ItemsSource="{Binding Companies}" >
                            <ContextMenu.ItemContainerStyle>
                                <Style TargetType="{x:Type MenuItem}">
                                    <Setter Property="ItemsSource" Value="{Binding Workers}"/>
                                    <Setter Property="Header" Value="{Binding CompanyName}"/>
                                    <Setter Property="ItemContainerStyle">
                                        <Setter.Value>
                                            <Style TargetType="{x:Type MenuItem}">
                                                <Setter Property="Header" Value="{Binding Name}"/>
                                                <Setter Property="Command" Value="{Binding MenuCommand}"/>
                                                <Setter Property="CommandParameter" Value="Box_110"/>
                                            </Style>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </ContextMenu.ItemContainerStyle>
                        </ContextMenu>
                    </TextBox.ContextMenu>
                </TextBox>

Pay attention to the CommandParameter property, this is the only property that is unique for each ContextMenu.
The menu itself is populated from ObservableCollection <Company> Companieswithin the ViewModel. The collection, in turn, is populated from a small XML file.
The Company class has a field containing the name of the company and a list of workers, which in turn have a name and a signature.
class Company 
    {
        public string CompanyName { get; set; }
        public ObservableCollection<Worker> Workers {get; set;}

        public Company(string displayName)
        {
            CompanyName = displayName;
            Workers = new ObservableCollection<Worker>();
        }


        public class Worker 
        {
            public string Name { get; set; }
            public string Signature { get; set; }

            public ICommand MenuCommand { get; set; }

            public Worker(string name, string signature)
            {
                MenuCommand = new Command(ContextMenuClick, CanExecuteMethod);
                Name = name;
                Signature = signature;
            }

            public void ContextMenuClick(object parameter)
            {
                switch (parameter)
                {                
                    case "Box_115":
                        MessageBox.Show("Ячейка номер 115");
                        StampDictionary.TextBoxes["115"].BoxValue = Name;
                        StampDictionary.TextBoxes["125"].BoxValue = Signature;

                        break;

                    case "Box_114":
                        MessageBox.Show("Ячейка номер 114");
                        StampDictionary.TextBoxes["114"].BoxValue = Name;
                        StampDictionary.TextBoxes["124"].BoxValue = Signature;
                        break;

                    case "Box_113":
                        MessageBox.Show("Ячейка номер 113");
                        StampDictionary.TextBoxes["113"].BoxValue = Name;
                        StampDictionary.TextBoxes["123"].BoxValue = Signature;
                        break;

                    case "Box_112":
                        MessageBox.Show("Ячейка номер 112");
                        StampDictionary.TextBoxes["112"].BoxValue = Name;
                        StampDictionary.TextBoxes["122"].BoxValue = Signature;
                        break;

                    case "Box_111":
                        MessageBox.Show("Ячейка номер 111");
                        StampDictionary.TextBoxes["111"].BoxValue = Name;
                        StampDictionary.TextBoxes["121"].BoxValue = Signature;
                        break;

                    case "Box_110":
                        MessageBox.Show("Ячейка номер 110");
                        StampDictionary.TextBoxes["110"].BoxValue = Name;
                        StampDictionary.TextBoxes["120"].BoxValue = Signature;
                        break;
                }
            }

            public bool CanExecuteMethod(object parameter)
            {
                return true;
            }
        }
    }

Nested class Worker has MenuCommand bound to ContextMenu
<Setter Property="Command" Value="{Binding MenuCommand}"/>
.
With a switch(parameter) statement, I check the CommandParameter ContextMenu of the TextBox in which it is called and insert the Name and Signature into the Dictionary that is bound to my TextBox via the Binding. (I need such a fraud with a dictionary for further actions with inserting values ​​into a drawing stamp via Kompas.Api)
Everything works fine. 5ca6290cce0a6876888469.png
5ca629a7b0102750030270.png5ca629e462b4c988863279.png
However, here's the problem that hit me. The ContextMenu code is repeated as many as 6 times (and it could have been more, for example), and only one CommandParameter is changed, and I firmly decided to try to change this state of affairs.
ContextMenu I put in Resources
<Page.Resources>
        <ContextMenu x:Key="MyContexMenu" ItemsSource="{Binding Companies}" >
            <ContextMenu.ItemContainerStyle>
                <Style TargetType="{x:Type MenuItem}">
                    <Setter Property="ItemsSource" Value="{Binding Workers}"/>
                    <Setter Property="Header" Value="{Binding CompanyName}"/>
                    <Setter Property="ItemContainerStyle">
                        <Setter.Value>
                            <Style TargetType="{x:Type MenuItem}">
                                <Setter Property="Header" Value="{Binding Name}"/>
                                <Setter Property="Command" Value="{Binding MenuCommand}"/>
                                <Setter Property="CommandParameter" Value="{Binding Path=Name, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TextBox}}}"/>
                            </Style>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ContextMenu.ItemContainerStyle>
        </ContextMenu>
       ...
</Page.Resources>

Notice the CommandParameter line. I'm (I don't even know how I could find this) here trying to bind the CommandParameter to the Name property of the TextBox to which the ContextMenu belongs.
TextBox in turn looks like this
<TextBox Name="Box_110" Grid.Column="2" Grid.Row="5" Text="{Binding TextBoxes[110].BoxValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                         ContextMenu="{StaticResource MyContexMenu}">

Here we see that through StaticResources I pull up the ContextMenu and see the Name of the TextBox, which is analogous to what the CommandParameter used to have.
And so, now let's see the screenshots, the method works, but not quite as we would like.
5ca62cc50d8e8989730061.png
5ca62d4ea9783611486520.png
5ca62d77eabb2584385982.png
And everything seems to be fine, right? Just like before.
However, if I try to select a name from, for example People, in another cell, then it will consider that the ContextMenu was called from the Box_110 cell, in which it was called for the first time.
5ca62dd279896976256324.png
5ca62dee28ae1666126753.png
It also works with everyone else. Calling a name in a cell from any menu will bind that menu to that cell only. The remaining menus, while not yet called, work adequately also until their first call.
So how can I solve this problem?
I have about two and a half months of programming experience in c#, this is my first big project and, probably, my implementation can be more called "creativity". However, I definitely want to make the ContextMenu work through StaticRecources, because it seems to me that my algorithm is ... normal)
I will take note of any hand swings in the direction where to dig.
Thank you.

Answer the question

In order to leave comments, you need to log in

1 answer(s)
F
Foggy Finder, 2019-04-05
@SpacePurr

You've run into a well-known binding problem in ContextMenu , ToolTip , because those elements are not part of the "visual" tree.
In such cases, two standard solutions are offered - use a PlacementTarget and a proxy object. The first one should fit perfectly.
Before showing the code, I'll note that instead of using the Name property as the key of the TextBox, it's better to use the Tag property .
That is, instead of: This is not a fundamental change, but in most of the answers for such cases, you will encounter the use of the Tag property. With this in mind, the binding for the CommandParameter
And a little offtopic:
In your ContextMenuClick handler you use a constant offset equal to 10 and the code is essentially the same for each case. You could shorten the code by first replacing the hint in Tag by removing the "Box_" prefix from there to get something like this:

public void ContextMenuClick(object param)
{
    if (int.TryParse(Convert.ToString(param), out int v))
    {
        StampDictionary.TextBoxes[v.ToString()].BoxValue = Name;
        StampDictionary.TextBoxes[(v + 10).ToString()].BoxValue = Signature;
    }
}

If all the keys in the dictionary are numeric, then it will be even easier to change the type of the key to int.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question