【问题标题】:How do I set IsThreeState=True in WPF MenuItem Checkbox?如何在 WPF MenuItem 复选框中设置 IsThreeState=True?
【发布时间】:2015-07-02 02:48:02
【问题描述】:

我希望有一个 MenuItem,其内部 CheckBox 的属性“IsThreeState”设置为 true。我想将它绑定到一个布尔值?在我的 ViewModel 中。

经过一番调查,我发现 found IsChecked 是一个普通的旧布尔值。

我的第一反应是添加一个附加的布尔值? MenuItem 的“IsCheckedThreeState”属性,但我仍然不知道如何解决内部 CheckBox 绑定到不可为空的 IsChecked 的事实。

如果没有更简单的方法,我可以创建一个新的控件模板并直接修改 CheckBox,但我认为这也需要子类化 MenuItem 以将 IsChecked 调整为可为空。

那么,我是否必须对 MenuItem 模板进行子类化/自定义才能获得我想要的功能,或者是否有一种我没有想到的更简单的方法。感谢您提供的任何帮助。

【问题讨论】:

  • 嗯。一个多小时没有回应。我发现最好的是“How to set/reset Three-state checkbox value in WPF”,其中的答案显示了如何以编程方式将值设置为null,但不为用户启用三态行为。想必您已经看到了这个问题 - 它并不能直接帮助您,但认为您可能会发现其中的一些有用。

标签: c# wpf menuitem


【解决方案1】:

如果你想保持原有的 MenuItem 检查样式,并且只想显示空值,而不是让用户设置它们,这可以通过转换器来完成:

  1. 一个双向转换器 ThreeStateToBooleanConverter,将 false/null/true 更改为 false/false/true(以及 false/true 到 false/true 的另一种方式),在将 MenuItem 绑定到 bool 时使用?财产。
  2. 一种单向转换器 IsNullToVisibilityConverter,可将 null 更改为 Visible,并将其他任何内容更改为 Collapsed,当值为 null 时,可用于绑定您希望在 MenuItem 的图标中显示的任何内容的 Visibility。

【讨论】:

    【解决方案2】:

    最简单的选择可能是通过Icon 属性插入一个复选框。

    <MenuItem Header="..." StaysOpenOnClick="True" Click="MenuItem_ToggleCheckBox">
        <MenuItem.Icon>
            <CheckBox HorizontalAlignment="Center" VerticalAlignment="Center" IsThreeState="True" />
        </MenuItem.Icon>
    </MenuItem>
    

    Click 事件处理程序中,您需要手动更新复选框(或复选框绑定到的某些属性)。

    【讨论】:

    • 我可以看到它是如何工作的,它不需要自定义的控制模板。不知道该图标可以是任何控件。人们应该注意的一个可能的问题是,MenuItem 的 IsCheckable 和 IsChecked 不能为 true 以使其正常工作。当任一为真时,控制模板会隐藏图标。感谢分享这个巧妙的小技巧。
    【解决方案3】:

    好的,所以我做了更多的挖掘工作。

    MenuItem 的控件模板甚至没有内部 CheckBox 控件。 MenuItem 使用它自己的 IsChecked 属性,它显示或隐藏一个内部 Path 控件来指示它的状态。

    所以,我更改了默认控制模板。我用 CheckBox 替换了 Path,并适当地重新连接了触发器:

    <ControlTemplate x:Key="CustomMenuItemControlTemplate" TargetType="{x:Type MenuItem}">
        <Grid SnapsToDevicePixels="True" MinWidth="225" MinHeight="26">
            <Rectangle x:Name="OuterBorder" RadiusY="2" RadiusX="2"/>
            <Rectangle x:Name="Bg" Fill="{TemplateBinding Background}" Margin="1" RadiusY="1" RadiusX="1" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="1"/>
            <Rectangle x:Name="InnerBorder" Margin="2"/>
            <DockPanel>
                <ContentPresenter x:Name="Icon" Content="{TemplateBinding Icon}" ContentSource="Icon" Margin="4,0,6,0" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
                <CheckBox x:Name="MenuCheckBox" Margin="7,4" Visibility="Hidden" VerticalAlignment="Center" IsThreeState="True" />
                <ContentPresenter ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" ContentStringFormat="{TemplateBinding HeaderStringFormat}" ContentSource="Header" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center" />
            </DockPanel>
            <Popup x:Name="PART_Popup" AllowsTransparency="True" Focusable="False" HorizontalOffset="1" IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}" Placement="Bottom" VerticalOffset="-1">
                <Themes:SystemDropShadowChrome x:Name="Shdw" Color="Transparent">
                    <Border x:Name="SubMenuBorder" BorderBrush="#FF959595" BorderThickness="1" Background="WhiteSmoke">
                        <ScrollViewer x:Name="SubMenuScrollViewer" Margin="1,0" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}">
                            <Grid RenderOptions.ClearTypeHint="Enabled">
                                <Canvas HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                                    <Rectangle x:Name="OpaqueRect" Fill="WhiteSmoke" Height="{Binding ActualHeight, ElementName=SubMenuBorder}" Width="{Binding ActualWidth, ElementName=SubMenuBorder}"/>
                                </Canvas>
                                <Rectangle Fill="#FFF1F1F1" HorizontalAlignment="Left" Margin="1,2" RadiusY="2" RadiusX="2" Width="28"/>
                                <Rectangle Fill="#FFE2E3E3" HorizontalAlignment="Left" Margin="29,2,0,2" Width="1"/>
                                <Rectangle Fill="White" HorizontalAlignment="Left" Margin="30,2,0,2" Width="1"/>
                                <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Cycle" Grid.IsSharedSizeScope="True" Margin="2" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" KeyboardNavigation.TabNavigation="Cycle"/>
                            </Grid>
                        </ScrollViewer>
                    </Border>
                </Themes:SystemDropShadowChrome>
            </Popup>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="IsSuspendingPopupAnimation" Value="True">
                <Setter Property="PopupAnimation" TargetName="PART_Popup" Value="None"/>
            </Trigger>
            <Trigger Property="Icon" Value="{x:Null}">
                <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsCheckable" Value="True">
                <Setter Property="Visibility" TargetName="MenuCheckBox" Value="Visible"/>
                <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
            </Trigger>
            <Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="True">
                <Setter Property="Margin" TargetName="Shdw" Value="0,0,5,5"/>
                <Setter Property="Color" TargetName="Shdw" Value="#71000000"/>
            </Trigger>
            <Trigger Property="IsKeyboardFocused" Value="True">
    
                <Setter Property="Fill" TargetName="Bg">
                    <Setter.Value>
                        <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
                            <GradientStop Color="#0462c9f5" Offset="0"/>
                            <GradientStop Color="#1C62c9f5" Offset="0.75"/>
                            <GradientStop Color="#3062c9f5" Offset="1"/>
                        </LinearGradientBrush>
                    </Setter.Value>
                </Setter>
                <Setter Property="Stroke" TargetName="OuterBorder" Value="#C062c9f5"/>
            </Trigger>
            <Trigger Property="IsEnabled" Value="False">
                <Setter Property="Foreground" Value="#FF9A9A9A"/>
            </Trigger>
            <Trigger Property="CanContentScroll" SourceName="SubMenuScrollViewer" Value="False">
                <Setter Property="Canvas.Top" TargetName="OpaqueRect" Value="{Binding VerticalOffset, ElementName=SubMenuScrollViewer}"/>
                <Setter Property="Canvas.Left" TargetName="OpaqueRect" Value="{Binding HorizontalOffset, ElementName=SubMenuScrollViewer}"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
    

    我在它自己的资源字典中有这个。您必须在项目中添加对“PresentationFramework.Aero”和以下 xmlns 的引用:

    xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
    

    然后,我将它们全部连接成一个样式:

    <Style TargetType="MenuItem">
        <Style.Resources>
            <Style TargetType="{x:Type CheckBox}">
                <Setter Property="IsChecked" Value="{Binding IsSelected}" />
            </Style>
        </Style.Resources>
        <Setter Property="Template" Value="{StaticResource CustomMenuItemControlTemplate}" />
        <Setter Property="IsCheckable" Value="True" />
        <Setter Property="IsChecked" Value="{Binding ToggleIsSelected}" />
        <Setter Property="StaysOpenOnClick" Value="True" />
    </Style>
    

    拼图的最后一块是绑定。我现在有两个属性:

    1) IsSelected - 我绑定到内部 CheckBox 的 IsChecked 属性的 bool?

    2) ToggleIsSelected - 我绑定到 MenuItem 的 IsChecked 属性的 bool

    拥有这两个绑定允许用户单击 MenuItem 中的任意位置来切换 CheckBox,如果 IsSelected 为空,则不会出现任何绑定错误。属性定义为:

    public bool? IsSelected
    {
        get
        {
            return _isSelected;
        }
        set
        {
            if (value == _isSelected)
                return;
            _isSelected = value ?? false;
            OnPropertyChanged();
            OnPropertyChanged("ToggleIsSelected");
        }
    }
    

    public bool ToggleIsSelected
    {
        get
        {
            return IsSelected ?? false;
        }
        set
        {
            if (value == IsSelected)
                return;
            IsSelected = value;
        }
    }
    

    这样做的一个潜在缺点是用户无法将 CheckBox 切换到它的 null 状态。就我而言,我只想编程将其设置为空,所以它对我有用。如果您需要用户切换为 null,请从您的 ViewModel 中删除 ToggleIsSelected 属性以及相关的 setter:

    <Setter Property="IsChecked" Value="{Binding ToggleIsSelected}" />
    

    ...并将 IsSelected 属性更改为:

    public bool? IsSelected
    {
        get
        {
            return _isSelected;
        }
        set
        {
            if (value == _isSelected)
                return;
            _isSelected = value;
        OnPropertyChanged();
        }
    }
    

    这也将取消单击任意位置进行切换的功能,因此您的用户将不得不直接检查 CheckBox...除非您想出其他方法来实现它。

    我知道这是一个很长的答案......但我认为其他人可能需要这个。如果可以的话,还不如为他们节省时间。如果您发现任何改进空间,请告诉我。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-14
      • 1970-01-01
      • 1970-01-01
      • 2023-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多