SpacePurr
@SpacePurr
c#, wpf

Как связать свойство Command Parameter у Context Menu со свойством Name у TextBox, к которому это меню привязано?

Здравствуйте.

Начну значит я с того, что у меня получилось, а потом плавно перейдем к тому, что у меня не получилось.

У меня есть 6 TextBox, которые имеют одинаковое 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>

Обратите внимание на свойство CommandParameter, это единственное свойство, которое уникально у каждого ContextMenu.

Само меню заполняется из ObservableCollection <Company> Companies, расположенном в ViewModel. Коллекция в свою очередь заполняется из небольшого XML файла.

Класс Company имеет поле, содержащее имя компании и список рабочих, которые в свою очередь имеют имя и подпись.
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;
            }
        }
    }


Вложенный класс Worker имеет MenuCommand привязанную к ContextMenu
<Setter Property="Command" Value="{Binding MenuCommand}"/>
.

С помощью оператора switch(parameter), я проверяю CommandParameter ContextMenu у TextBox, в котором оно вызвано и выполняю вставку Name и Signature в Dictionary, который через Binding связан с моим TextBox. (Такая махинация со словарем мне нужна для дальнейших действий со вставкой значений в штамп чертежа через Kompas.Api)

Все работает прекрасно. 5ca6290cce0a6876888469.png
5ca629a7b0102750030270.png5ca629e462b4c988863279.png

Однако вот проблема меня задела. Код ContextMenu повторяется целых 6 раз(а могло быть и больше, например), а изменяется только один CommandParameter и я твердо решил постараться изменить такое положение вещей.

ContextMenu я положил в 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>


Обратите внимание на строку CommandParameter. Я (даже не знаю как я смог такое отрыть) здесь пытаюсь привязать CommandParameter к свойству Name у TextBox, которому принадлежит ContextMenu.
TextBox в свою очередь выглядит так
<TextBox Name="Box_110" Grid.Column="2" Grid.Row="5" Text="{Binding TextBoxes[110].BoxValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                         ContextMenu="{StaticResource MyContexMenu}">


Здесь мы видим, что через StaticResources я подтягиваю ContextMenu и видим Name у TextBox, которое является аналогом того, что раньше было у CommandParameter.

И так, теперь посмотрим скрины, метод работает, но не совсем так как хотелось бы.
5ca62cc50d8e8989730061.png
5ca62d4ea9783611486520.png
5ca62d77eabb2584385982.png

И вроде бы все хорошо да? Прямо как и раньше.
Однако если я постараюсь в другую ячейку выбрать имя из, например Люди, то он посчитает, что ContextMenu вызвано у ячейки Box_110, в котором оно было вызвано в первый раз.
5ca62dd279896976256324.png
5ca62dee28ae1666126753.png

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

Так как же мне решить эту проблему?
Опыта программирования на c# у меня около двух с половиной месяцев, это мой первый большой проект и, наверное, мою реализацию больше можно назвать "творчеством". Однако я точно хочу заставить работать ContextMenu через StaticRecources, потому что мне кажется, что мой алгоритм...нормальный)
Приму к сведению любые махи рукой в сторону, куда копать.

Спасибо.
  • Вопрос задан
  • 649 просмотров
Решения вопроса 1
FoggyFinder
@FoggyFinder
Вы столкнулись с широко известной проблемой привязки в ContextMenu, ToolTip из-за того что эти элементы не являются частью "визуального" дерева (visual tree).

В таких случаях предлагается два стандартных решениях - использовать PlacementTarget и прокси-объект. Первое тут должно подойти идеально.

Прежде чем показать код отмечу, что вместо использования свойства Name в качестве ключа TextBox лучше использовать свойство Tag.

То есть вместо:

<TextBox Name="Box_110" ...>

<TextBox Tag="Box_110" ...>

Это не принципиальное изменение, но в большинстве ответов для подобных случаев вы встретите использование именно свойство Tag.

С учетом этого привязка для CommandParameter будет выглядеть вот так:

<Setter Property="CommandParameter" Value="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}" />


И немного оффтопа:

В вашем обработчике ContextMenuClick вы используете постоянное смещение равное 10 и код по сути для каждого кейса одинаков. Вы могли бы сократить код, предварительно заменив подсказку в Tag убрав оттуда префикс "Box_" чтобы получилось что-то подобное:

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;
    }
}


Если все ключи в словаре числовые, то еще проще будет заменит тип ключа на int.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы
НПК «Катрен» Новосибирск
от 70 000 ₽
ФинГрад Москва
от 80 000 до 150 000 ₽
Випакс Пермь
от 80 000 до 150 000 ₽
03 июл. 2020, в 16:27
45000 руб./за проект
03 июл. 2020, в 16:16
26250 руб./за проект
03 июл. 2020, в 16:00
7000 руб./за проект