Как связать несколько кнопок в Combobox - PullRequest
0 голосов
/ 19 апреля 2020

enter image description here

Я хочу связать список кнопок в полях со списком. Каждый Combobox будет содержать кнопки одной категории и так далее. Как указано в прикрепленном изображении.

Ниже приведен мой код:

<ItemsControl x:Name="iNumbersList">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal" MaxWidth="930"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button  Click="ItemButtonClick"
                 Tag="{Binding ItemTag}"
                 HorizontalContentAlignment="Center"
                 VerticalContentAlignment="Center"
                 Height="100" Width="300">
                <TextBlock TextAlignment="Center" Foreground="Red"
                       HorizontalAlignment="Center" FontWeight="SemiBold"
                       FontSize="25" TextWrapping="Wrap"
                       Text="{Binding ItemDisplayMember}"/>
            </Button>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
public class NumberModel
{
    public string ItemDisplayMember { get; set; }
    public object ItemTag { get; set; }
    public string ItemCategory { get; set; }
}

Как сгруппировать свойство ItemCategory и связать его с GUI, ComboBox для каждого ItemCategory, а затем несколько кнопок в нем?

1 Ответ

2 голосов
/ 19 апреля 2020

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

Я написал простой пример, используя MVVM программирование шаблон. Будет много новых классов, но большинство из них вам нужно добавить в проект только один раз. Давайте go с нуля!

1) Создайте класс NotifyPropertyChanged для реализации INotifyPropertyChanged интерфейса. Необходимо Binding для динамического обновления макета во время выполнения.

NotifyPropertyChanged.cs

public class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

2) Создать MainViewModel класс, производный от NotifyPropertyChanged. Он будет использоваться для Binding целевых свойств.

MainViewModel.cs

public class MainViewModel : NotifyPropertyChanged
{
    public MainViewModel()
    {

    }
}

3) Присоединить MainViewModel к MainWindow DataContext , Один из способов - сделать это в xaml.

MainWindow.xaml

<Window.DataContext>
    <local:MainViewModel/>
</Window.DataContext>

4) Извлечь класс данных NumberModel из NotifyPropertyChanged и добавить OnPropertyChanged Позвоните для каждого имущества. Это даст замечательный эффект: когда вы изменяете любое свойство во время выполнения, вы сразу увидите изменения в пользовательском интерфейсе. MVVM Волхвы c под названием Binding:)

NumberModel.cs

public class NumberModel : NotifyPropertyChanged
{
    private string _itemDisplayMember;
    private object _itemTag;
    private string _itemCategory;

    public string ItemDisplayMember
    { 
        get => _itemDisplayMember;
        set 
        {
            _itemDisplayMember = value;
            OnPropertyChanged();
        }
    }
    public object ItemTag
    {
        get => _itemTag;
        set
        {
            _itemTag = value;
            OnPropertyChanged();
        }
    }
    public string ItemCategory
    {
        get => _itemCategory;
        set
        {
            _itemCategory = value;
            OnPropertyChanged();
        }
    }
}

5) При нажатии кнопки я не буду обработать событие Click, но вызвать Command. Для удобства использования команд я предлагаю пересылать его класс logi c (захвачен здесь ).

RelayCommand.cs

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Func<object, bool> _canExecute;

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

    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
    public void Execute(object parameter) => _execute(parameter);
}

6) Все готово для заполнения MainViewModel с кодом. Я добавил туда команду и несколько предметов для сбора.

MainViewModel.cs

public class MainViewModel : NotifyPropertyChanged
{
    private ObservableCollection<NumberModel> _itemsList;
    private ICommand _myCommand;

    public ObservableCollection<NumberModel> ItemsList
    {
        get => _itemsList;
        set
        {
            _itemsList = value;
            OnPropertyChanged();
        }
    }

    public ICommand MyCommand => _myCommand ?? (_myCommand = new RelayCommand(parameter =>
    {
        if (parameter is NumberModel number) 
            MessageBox.Show("ItemDisplayMember: " + number.ItemDisplayMember + "\r\nItemTag: " + number.ItemTag.ToString() + "\r\nItemCategory: " + number.ItemCategory);
    }));

    public MainViewModel()
    {
        ItemsList = new ObservableCollection<NumberModel>
        {
            new NumberModel { ItemDisplayMember = "Button1", ItemTag="Tag1", ItemCategory = "Category1" },
            new NumberModel { ItemDisplayMember = "Button2", ItemTag="Tag2", ItemCategory = "Category1" },
            new NumberModel { ItemDisplayMember = "Button3", ItemTag="Tag3", ItemCategory = "Category1" },
            new NumberModel { ItemDisplayMember = "Button4", ItemTag="Tag4", ItemCategory = "Category2" },
            new NumberModel { ItemDisplayMember = "Button5", ItemTag="Tag5", ItemCategory = "Category2" },
            new NumberModel { ItemDisplayMember = "Button6", ItemTag="Tag6", ItemCategory = "Category2" },
            new NumberModel { ItemDisplayMember = "Button7", ItemTag="Tag7", ItemCategory = "Category3" },
            new NumberModel { ItemDisplayMember = "Button8", ItemTag="Tag8", ItemCategory = "Category4" },
            new NumberModel { ItemDisplayMember = "Button9", ItemTag="Tag9", ItemCategory = "Category4" }
        };
    }
}

7) Основной ответ на ваш главный вопрос: используйте IValueConverter, чтобы отфильтровать список с требуемыми критериями. Я написал 2 конвертера. Сначала для категорий, затем для кнопок.

Converters.cs

public class CategoryConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is ObservableCollection<NumberModel> collection)
        {
            List<NumberModel> result = new List<NumberModel>();
            foreach (NumberModel item in collection)
            {
                if (!result.Any(x => x.ItemCategory == item.ItemCategory))
                    result.Add(item);
            }
            return result;
        }
        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public class ItemGroupConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values[0] is ObservableCollection<NumberModel> collection && values[1] is string categoryName)
        {
            List<NumberModel> result = new List<NumberModel>();
            foreach (NumberModel item in collection)
            {
                if (item.ItemCategory == categoryName)
                    result.Add(item);
            }
            return result;
        }
        return null;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

8) Теперь все готово для заполнения разметки. Я публикую полную разметку здесь, чтобы все было понятно.

Примечание: Я столкнулся с Visual Studio 2019 16.5.4 cra sh при установке MultiBinding в ItemsSource и применил обходной путь.

MainWindow.xaml

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApp1"
        Title="MainWindow" Height="600" Width="1000" WindowStartupLocation="CenterScreen">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <local:CategoryConverter x:Key="CategoryConverter"/>
        <local:ItemGroupConverter x:Key="ItemGroupConverter"/>
    </Window.Resources>
    <Grid>
        <ItemsControl ItemsSource="{Binding ItemsList, Converter={StaticResource CategoryConverter}}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel Orientation="Vertical"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate DataType="{x:Type local:NumberModel}">
                    <Expander Header="{Binding ItemCategory}">
                        <ItemsControl DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType=Window}}">
                            <ItemsControl.Style>
                                <Style TargetType="ItemsControl">
                                    <Setter Property="ItemsSource">
                                        <Setter.Value>
                                            <MultiBinding Converter="{StaticResource ItemGroupConverter}">
                                                <Binding Path="ItemsList"/>
                                                <Binding Path="Header" RelativeSource="{RelativeSource AncestorType=Expander}"/>
                                            </MultiBinding>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </ItemsControl.Style>
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <WrapPanel HorizontalAlignment="Left" VerticalAlignment="Center" Orientation="Horizontal" MaxWidth="930"/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                            <ItemsControl.ItemTemplate>
                                <DataTemplate DataType="{x:Type local:NumberModel}">
                                    <Button Tag="{Binding ItemTag}"
                                            HorizontalContentAlignment="Center"
                                            VerticalContentAlignment="Center"
                                            Height="100" Width="300"
                                            Command="{Binding DataContext.MyCommand,RelativeSource={RelativeSource AncestorType=Window}}"
                                            CommandParameter="{Binding}">
                                        <TextBlock TextAlignment="Center" Foreground="Red"
                                                   HorizontalAlignment="Center" FontWeight="SemiBold"
                                                   FontSize="25" TextWrapping="Wrap"
                                                   Text="{Binding ItemDisplayMember}"/>
                                    </Button>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>
                    </Expander>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

Работа выполнена с MVVM . Наслаждаться. :)

Screenshot

PS Ах, да, я забыл показать вам класс code-behind. Вот оно!

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...