简体   繁体   English

如何在WPF MenuItem复选框中设置IsThreeState = True?

[英]How do I set IsThreeState=True in WPF MenuItem Checkbox?

I'm looking to have a MenuItem whose internal CheckBox has the property "IsThreeState" set to true. 我希望有一个MenuItem,其内部CheckBox的属性“IsThreeState”设置为true。 I want to bind it to a bool? 我想将它绑定到一个bool? in my ViewModel. 在我的ViewModel中。

After looking into it a bit, I've found that IsChecked is a plain old bool. 在仔细研究之后,我发现 IsChecked是一个普通的老布尔。

My first instinct is to add an attached bool? 我的第一直觉是添加一个附加的bool? "IsCheckedThreeState" property to the MenuItem, but I still can't figure out how to get around the fact that the internal CheckBox is bound to the non-nullable IsChecked. MenuItem的“IsCheckedThreeState”属性,但我仍然无法弄清楚如何解决内部CheckBox绑定到不可空的IsChecked的事实。

If there is no easier way, I could create a new control template and modify the CheckBox directly, but I think that would also require sub-classing MenuItem to adjust the IsChecked to be nullable. 如果没有更简单的方法,我可以创建一个新的控件模板并直接修改CheckBox,但我认为还需要对MenuItem进行子类化以将IsChecked调整为可为空。

So, will I have to subclass/customize the MenuItem template to get the functionality I want, or is there an easier way that I'm not thinking of. 那么,我是否必须子类化/自定义MenuItem模板以获得我想要的功能,或者是否有一种我没想到的更简单的方法。 Thanks for any help you might be able to provide. 感谢您提供的任何帮助。

Alright, so I've done some more digging. 好吧,所以我做了一些挖掘。

The Control Template for the MenuItem doesn't even have a internal CheckBox control. MenuItem的控件模板甚至没有内部CheckBox控件。 The MenuItem uses it's own IsChecked property, and it shows or hides an internal Path control to indicate it's state. MenuItem使用它自己的IsChecked属性,它显示或隐藏一个内部Path控件来指示它的状态。

So, I altered the default Control Template. 所以,我改变了默认的控制模板。 I replaced the Path with a CheckBox, and I rewired the triggers appropriately: 我用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>

I have this in it's own Resource Dictionary. 我有它自己的资源字典。 You'll have to add a reference in your project to "PresentationFramework.Aero" and the following xmlns: 您必须在项目中向“PresentationFramework.Aero”和以下xmlns添加引用:

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

Then, I wire it all up in a style: 然后,我把它连接成一个样式:

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

The final piece of the puzzle is the bindings. 拼图的最后一部分是绑定。 I have two properties now: 我现在有两个属性:

1) IsSelected - a bool? 1)IsSelected - bool? that I'm binding to the internal CheckBox's IsChecked property 我绑定到内部CheckBox的IsChecked属性

2) ToggleIsSelected - a bool which I'm binding to the MenuItem's IsChecked property 2)ToggleIsSelected - 我绑定到MenuItem的IsChecked属性的bool

Having these two bindings allows the user to click anywhere in the MenuItem to toggle the CheckBox, without having any binding errors if IsSelected is null. 拥有这两个绑定允许用户单击MenuItem中的任意位置以切换CheckBox,如果IsSelected为null,则不会出现任何绑定错误。 The properties are defined as: 属性定义为:

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

and

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

One potential downside to doing it this way is that the users can't toggle the CheckBox to it's null state. 这样做的一个潜在缺点是用户无法将CheckBox切换到它的null状态。 In my case, I only want to program to set it to null, so it works for me. 在我的情况下,我只想编程将其设置为null,因此它适用于我。 If you need the users to toggle null, remove the ToggleIsSelected property from your ViewModel, and the associated setter: 如果您需要用户切换null,请从ViewModel和相关的setter中删除ToggleIsSelected属性:

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

...and change the IsSelected property to: ...并将IsSelected属性更改为:

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

This will also remove the ability to click anywhere to toggle, so your users will have to check the CheckBox directly... unless you come up with some other way to implement it. 这也将删除单击任意位置以切换的功能,因此您的用户必须直接检查CheckBox ...除非您想出一些其他方法来实现它。

I know that this is a long answer... but I figured someone else might have a need for this. 我知道这是一个很长的答案......但我认为其他人可能需要这个。 Might as well save them the time if I can. 如果可以的话,还可以节省他们的时间。 If you can see any room for improvements, please let me know. 如果您能看到任何改进空间,请告诉我。

The simplest option is probably to insert a checkbox through the Icon property. 最简单的选项可能是通过Icon属性插入一个复选框。

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

In the Click event handler, you'll need to manually update the check box (or some property to which the check box is bound). Click事件处理程序中,您需要手动更新复选框(或复选框绑定到的某个属性)。

If you want to keep the original MenuItem check style and only want to display null values, not let the user set them, this can be done with converters: 如果您想保留原始的MenuItem检查样式并且只想显示空值,而不是让用户设置它们,可以使用转换器完成:

  1. A two-way converter ThreeStateToBooleanConverter that changes false/null/true to false/false/true (and false/true to false/true the other way), which is used when binding the MenuItem to the bool? 一个双向转换器ThreeStateToBooleanConverter,它将false / null / true更改为false / false / true(而false / true更改为false / true,另一种方式),将MenuItem绑定到bool时使用? property. 属性。
  2. A one-way converter IsNullToVisibilityConverter that changes null to Visible, and anything else to Collapsed, which can be used to bind the Visibility of whatever you wish to show in the Icon of the MenuItem when the value is null. 单向转换器IsNullToVisibilityConverter将null更改为Visible,以及其他任何更改为Collapsed,可用于绑定当值为null时您希望在MenuItem的Icon中显示的任何内容的Visibility。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM