【问题标题】:Add button to comboBox with MVVM使用 MVVM 将按钮添加到组合框
【发布时间】:2016-10-11 12:43:41
【问题描述】:

我正在使用 MVVM 模式,并有一个 ComboBox 绑定到视图模型中的属性,如下所示:

<ComboBox ItemsSource="{Binding Path=ItemCollection}"
          SelectedItem="{Binding Path=SelectedItem}">    
    <ComboBox.ItemTemplate>
        <DataTemplate>
             <!-- Custom combobox item template -->
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

这很好用,在DataTemplate 中我可以指定每个项目的显示方式。

我想在组合框下拉 itempresenter 的末尾添加一个按钮。 类似于 MS Word 在下图中的完成方式。

当按下选定的“更多列...”按钮时,会显示一个对话框,用户可以输入详细信息。我正在尝试归档相同的工作流程。

【问题讨论】:

  • 供将来参考“自定义组合框项目模板”不正确,您将得到一个 ComboBoxItem,其内容属于您的模板,如果您想更改实际的 ComboBoxItem,您需要使用风格

标签: wpf xaml mvvm combobox


【解决方案1】:

我不认为ComboBox 在这里是正确的选择,因为ComboBox 的自然行为是选择您想要打开对话框时单击的项目

这是一个完整的代码示例,说明如何实现这样的功能: 1. 创建一个 DropDownButton 行为如下

    public class DropDownButtonBehavior : Behavior<Button>
    {
    private long attachedCount;
    private bool isContextMenuOpen;

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.AddHandler(Button.ClickEvent, new RoutedEventHandler(AssociatedObject_Click), true);
    }

    void AssociatedObject_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        Button source = sender as Button;
        if (source != null && source.ContextMenu != null)
        {
            // Only open the ContextMenu when it is not already open. If it is already open,
            // when the button is pressed the ContextMenu will lose focus and automatically close.
            if (!isContextMenuOpen)
            {
                source.ContextMenu.AddHandler(ContextMenu.ClosedEvent, new RoutedEventHandler(ContextMenu_Closed), true);
                Interlocked.Increment(ref attachedCount);
                // If there is a drop-down assigned to this button, then position and display it 
                source.ContextMenu.PlacementTarget = source;
                source.ContextMenu.Placement = PlacementMode.Bottom;
                source.ContextMenu.IsOpen = true;
                isContextMenuOpen = true;
            }
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.RemoveHandler(Button.ClickEvent, new RoutedEventHandler(AssociatedObject_Click));
    }

    void ContextMenu_Closed(object sender, RoutedEventArgs e)
    {
        isContextMenuOpen = false;
        var contextMenu = sender as ContextMenu;
        if (contextMenu != null)
        {
            contextMenu.RemoveHandler(ContextMenu.ClosedEvent, new RoutedEventHandler(ContextMenu_Closed));
            Interlocked.Decrement(ref attachedCount);
        }
    }
}
  1. 为按钮创建模板

    <Button>
        <i:Interaction.Behaviors>
            <local:DropDownButtonBehavior/>
        </i:Interaction.Behaviors>
        <Button.Content>
            <StackPanel Orientation="Horizontal">
                <ContentControl Content="{Binding SelectedItem}"/>
                <Separator Margin="2,0">
                    <Separator.LayoutTransform>
                        <TransformGroup>
                            <TransformGroup.Children>
                                <TransformCollection>
                                    <RotateTransform Angle="90"/>
                                </TransformCollection>
                            </TransformGroup.Children>
                        </TransformGroup>
                    </Separator.LayoutTransform>
                </Separator>
                <Path Margin="2" VerticalAlignment="Center" Width="6" Fill="#FF527DB5" Stretch="Uniform" HorizontalAlignment="Right" Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z "/>
            </StackPanel>
        </Button.Content>
        <Button.ContextMenu>
            <ContextMenu>
                <ContextMenu.ItemContainerStyle>
                    <Style TargetType="MenuItem">
                        <Setter Property="Command" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type Button}, Mode=FindAncestor}, Path=DataContext.SelectionChangedCommand}" />
                        <Setter Property="CommandParameter" Value="{Binding}"></Setter>
                    </Style>
                </ContextMenu.ItemContainerStyle>
                <ContextMenu.ItemsSource>
                    <CompositeCollection>
                        <CollectionContainer Collection="{Binding Source={StaticResource MyData}}" />
                        <MenuItem Header="More"
                                  Command="{Binding MoreButtonCommand}"/>
                    </CompositeCollection>
                </ContextMenu.ItemsSource>
            </ContextMenu>
        </Button.ContextMenu>
    </Button>
    
  2. 视图模型

    public class MainWindowViewModel : BindableBase
    {
    private MyData _selectedItem;
    
    public MainWindowViewModel()
    {
        Collection = new ObservableCollection<MyData>
        {
            new MyData {Data = "aaa"},
            new MyData {Data = "bbb"},
        };
        SelectedItem = Collection.First();
        // This is important. It changes the selected items upon menu item click
        SelectionChangedCommand = new DelegateCommand<MyData>(data => SelectedItem = data);
        MoreButtonCommand = new DelegateCommand(() => {} /* Launch dialog ... */);
    }
    
    public ObservableCollection<MyData> Collection { get; set; }
    
    public MyData SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            _selectedItem = value;
            OnPropertyChanged(() => SelectedItem);
        }
    }
    public DelegateCommand<MyData> SelectionChangedCommand { get; set; }
    public DelegateCommand MoreButtonCommand { get; set; }
    

    }

让我解释一下这里发生了什么。 每次单击按钮都会打开一个类似于ComboBox 行为的下拉菜单。此列表中显示的项目是由绑​​定项目和静态项目生成的MenuItems。从ItemSource 创建的那些会向 ViewModel 启动一个 selectionChanged 命令,从而导致所选项目发生变化,而静态的则可以启动您指定给它的任何命令。

希望对你有帮助

【讨论】:

    【解决方案2】:

    我需要创建一个带有嵌入式搜索/过滤功能的自定义下拉菜单,并通过创建一个在按下时会显示弹出窗口的按钮来实现。弹出窗口有一个项目列表框,并通过使用网格进行布局在顶部和底部显示一些按钮。我在这里为你简化了一点:

    <UserControl x:Class="CustomDropDown"
                 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:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
                 mc:Ignorable="d"  x:Name="Root" 
                 d:DesignHeight="300" d:DesignWidth="300">
        <Grid>
            <Button x:Name="DropDownButton" Click="DropDownButton_Click" HorizontalContentAlignment="Stretch">
                <DockPanel HorizontalAlignment="Stretch" LastChildFill="False" Margin="2,0">
                    <TextBlock DockPanel.Dock="Left" Text="{Binding ElementName=Root, Path=Header}"/>
                    <TextBlock DockPanel.Dock="Right" Text="▼"/>
                </DockPanel>
            </Button>
            <Popup x:Name="DropDownPopup" Placement="Bottom" Focusable="False" StaysOpen="False"
                    Width="{Binding ElementName=DropDownButton, Path=ActualWidth}" MinWidth="250"
                    Height="Auto" AllowsTransparency="True">
                <Border Padding="5" Background="White"  Margin="0,0,8,8"
                        BorderBrush="Silver" CornerRadius="0,0,5,5" BorderThickness="1">
                    <Border.Effect>
                        <DropShadowEffect BlurRadius="5" Opacity="0.5"/>
                    </Border.Effect>
                    <Grid HorizontalAlignment="Stretch" Background="White">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <ListBox Grid.Row="0" MaxHeight="150" x:Name="DropDownList" SelectionMode="Extended"
                                ItemsSource="{Binding ElementName=Root,  Path=ItemsSource}"
                                DisplayMemberPath="{Binding ElementName=Root, Path=DisplayMemberPath}">
                            <ListBox.ItemContainerStyle>
                                <Style TargetType="{x:Type ContentControl}">
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                                <!-- Item Style -->
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </ListBox.ItemContainerStyle>
                        </ListBox>
                        <Grid>
                            <!-- More Columns Button Here -->
                        </Grid>
                    </Grid>
                </Border>
            </Popup>
        </Grid>
    </UserControl>
    

    因为我将它设置为 UserControl,所以我有一些依赖属性,例如 Header、DisplayMemberPath 等,我希望能够在 XAML 中使用控件时定义绑定。 SelectedItems 有自己的依赖属性,并且在后面的代码中注册了一个事件以保持一切同步。

    public IRangeCollection SelectedItems
    {
        get { return (IRangeCollection)GetValue(SelectedItemsProperty); }
        set { SetValue(SelectedItemsProperty, value); }
    }
    
    // Using a DependencyProperty as the backing store for SelectedItems.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedItemsProperty =
        DependencyProperty.Register("SelectedItems",
            typeof(IRangeCollection), typeof(CustomDropDown),
            new PropertyMetadata(new PropertyChangedCallback(SelectedItemsPropertyChanged)));
    
    private static void SelectedItemsPropertyChanged(DependencyObject sender,
        DependencyPropertyChangedEventArgs e)
    {
        CustomDropDown dropDown = (CustomDropDown)sender;
        dropDown.UpdateDropDownFromSelectedItems();
        dropDown.UpdateSelectedItemsCollectionChangedHandler(e.OldValue, e.NewValue);
    }
    
    private void UpdateSelectedItemsCollectionChangedHandler(object oldValue, object newValue)
    {
        if (oldValue != null && oldValue is INotifyCollectionChanged)
        {
            ((INotifyCollectionChanged)oldValue).CollectionChanged -= SelectedItems_CollectionChanged;
        }
        if (newValue != null && newValue is INotifyCollectionChanged)
        {
            ((INotifyCollectionChanged)newValue).CollectionChanged += SelectedItems_CollectionChanged;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-15
      • 1970-01-01
      • 1970-01-01
      • 2014-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多