Привязка динамического списка к столбцу ComboBoxes в DataGrid работает только до фактического отображения списка - PullRequest
0 голосов
/ 27 января 2019

Я работаю над приложением WPF по шаблону MVVM и сталкиваюсь с проблемой, которую я абстрагировал в приведенном ниже коде. Приложение содержит DataGrid с 2 столбцами ComboBox (каждый генерируется по-разному). Цель состоит в том, чтобы ComboBox представлял только те элементы, которые еще не были выбраны другими ComboBox в том же столбце.

Комбо-ящики привязаны к наблюдаемой коллекции профессий. Каждая профессия имеет логическое значение «Выбираемый», и ComboBox должен показывать только те записи со значением «true».

Список содержит:

  • Живописец
  • Поэт
  • Scientist

Чтобы смоделировать интерактивную Команду из XAML в ViewModel, я поместил кнопку, которая установит для Выбираемого Ученого значение «ложь».

App.xaml:

<Application x:Class="wpf_ComboBoxColumn.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
</Application>

MainWindow.xaml.cs:

using System.Windows;

namespace wpf_ComboBoxColumn
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainViewModel();
        }
    }
}

MainWindow.xaml:

<Window x:Class="wpf_ComboBoxColumn.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:wpf_ComboBoxColumn"
        xmlns:viewModel="clr-namespace:wpf_ComboBoxColumn"
        Title="Combobox Column Binding" Height="350" Width="460">
    <Window.Resources>
        <ResourceDictionary>
            <Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
                <Setter Property="ItemsSource"
                        Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
                <Setter Property="ItemContainerStyle">
                    <Setter.Value>
                        <Style TargetType="ComboBoxItem" BasedOn="{StaticResource {x:Type ComboBoxItem}}">
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding Selectable}" Value="False">
                                    <Setter Property="Visibility" Value="Collapsed" />
                                </DataTrigger>
                                <DataTrigger Binding="{Binding Selectable}" Value="True">
                                    <Setter Property="Visibility" Value="Visible" />
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Setter.Value>
                </Setter>
            </Style>
        </ResourceDictionary>
    </Window.Resources>
    <Grid>
        <Grid.DataContext>
            <viewModel:MainViewModel />
        </Grid.DataContext>
        <DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
                <DataGridComboBoxColumn
                    Header="ComboBoxColumn"
                    SelectedValueBinding="{Binding Description, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
                    SelectedValuePath="Description"
                    DisplayMemberPath="Description"
                    >
                    <DataGridComboBoxColumn.ElementStyle>
                        <Style TargetType="ComboBox">
                            <Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Professions}"/>
                        </Style>
                    </DataGridComboBoxColumn.ElementStyle>
                    <DataGridComboBoxColumn.EditingElementStyle>
                        <Style TargetType="ComboBox">
                            <Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Professions}"/>
                        </Style>
                    </DataGridComboBoxColumn.EditingElementStyle>
                </DataGridComboBoxColumn>
                <DataGridTemplateColumn Header="TemplateColumn">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox 
                                ItemsSource="{Binding Path=DataContext.Professions, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
                                DisplayMemberPath="Description"
                                SelectedValue="{Binding Profession, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
                                >
                            </ComboBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
        <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="210,290,0,0" VerticalAlignment="Top" Width="75" Command="{Binding DebugCommand}"/>
    </Grid>
</Window>

CustomCommand.cs (реализация ICommand):

using System;
using System.Windows.Input;

namespace wpf_ComboBoxColumn
{
    public class CustomCommand: ICommand
    {
        private readonly Action<object> execute;

        public CustomCommand(Action<object> execute)
        {
            this.execute = execute;
        }

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

        public void Execute(object parameter)
        {
            execute(parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add => CommandManager.RequerySuggested += value;
            remove => CommandManager.RequerySuggested -= value;
        }
    }
}

MainViewModel.cs:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace wpf_ComboBoxColumn
{
    public class NotifyUIBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public void RaisePropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class Profession
    {
        public string Description { get; set; }
        public Boolean Selectable { get; set; }
    }

    public class Person
    {
        public string Name { get; set; }
        public string Profession { get; set; }
    }

    public class MainViewModel : NotifyUIBase
    {
        public ObservableCollection<Person> People { get; set; }
        public ObservableCollection<Profession> Professions { get; set; }
        public ICommand DebugCommand { get; set; }

        public MainViewModel()
        {
            DebugCommand = new CustomCommand(Debug);

            People = new ObservableCollection<Person>
            {
                new Person{Name="Tom", Profession="" },
                new Person{Name= "Dick", Profession="" },
                new Person{Name= "Harry", Profession="" }
            };

            Professions = new ObservableCollection<Profession>
            {
                new Profession{ Description="Painter", Selectable=true},
                new Profession{ Description="Poet", Selectable=true},
                new Profession{ Description="Scientist", Selectable=true},
            };
        }

        private void Debug(object obj)
        {
            Professions[2].Selectable = false;
        }
    }
}

Теперь рассмотрим следующий сценарий (я все еще пытаюсь выяснить, как включить снимки экрана):

  • Откройте приложение: это покажет сетку с 3 столбцами: Первый столбец показывает имена «Том», «Дик» и «Гарри». Второй столбец содержит ComboBox для каждого человека. Требуется несколько кликов, чтобы открыть. Третий столбец также содержит ComboBox для каждого человека. Этот узнаваем как таковой.
  • Выберите "Ученый" для Тома
  • Нажмите кнопку (чтобы подделать, что мы выполнили код, который изменил Profession.Selectable)
  • Нажмите на поле со списком для Дика
  • Это действительно покажет оставшиеся Профессии (без Ученого) для самого правого столбца ComboBoxes. В крайнем левом столбце по-прежнему отображаются все параметры, поэтому этот параметр сразу не работает.
  • Снова нажмите на комбобокс для Тома
  • Это, даже для самого правого столбца ComboBox, снова покажет все опции (или, скорее: все еще)!

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

Вопрос: есть ли способ принудительно обновить ItemsSource? Желательно, конечно, уважать MVVM, но на этом этапе я остановлюсь на любом рабочем решении, использующем любой тип ComboBox.

Спасибо!

1 Ответ

0 голосов
/ 28 января 2019

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...