65.9K
CodeProject 正在变化。 阅读更多。
Home

将控件切换为整个窗口的只读状态

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (8投票s)

2014 年 4 月 24 日

CPOL

9分钟阅读

viewsIcon

21112

downloadIcon

1272

通过单击菜单项将窗口上的所有控件切换为只读

引言

是否曾经希望能够一键将屏幕上的所有控件切换为只读模式?本文将介绍两种实现此目的的类似方法。第一种解决方案将使用自定义的依赖项属性,而第二种解决方案将使用自定义的附加依赖项属性

背景

在搜索有关此主题的信息时,我发现了一些关于如何创建全局只读“开关”的答案。它们几乎都说要使用依赖项属性,但很少或根本没有提供如何实现此目的的代码。本文将展示代码并解释如何实现这个简单的功能。代码是用 VB.NET 编写的,但可以使用代码转换器站点轻松转换为 C#。

代码解释 - 解决方案 1

我创建了一个简单的 WPF(MVVM 类型)项目来演示代码。为了保持代码简单,我删除了许多 MVVM 框架。我包含了Josh Smith 的中继命令,以便处理菜单项的禁用。(下面将进行解释)。

该项目包含一个主窗口控件,其中包含菜单栏、内容控件和一个按钮,用于在两个用户控件之间切换。第一个用户控件包含多个文本框,通过单击菜单项可以切换它们的只读状态。第二个用户控件包含一个按钮,没有文本框,用于显示如何为每个新用户控件重置只读开关,并显示如何禁用菜单项,因为它对没有文本框的用户控件没有影响。

创建自定义依赖项属性

首先,我们需要为该主窗口创建一个自定义依赖项属性。它将是一个 Boolean 属性,我们可以将其链接到我们控件的只读属性。

    'Dependency Property
    Public Shared ReadOnly DPIsReadOnlyProperty As DependencyProperty = _
                DependencyProperty.Register("DPIsReadOnly", GetType(Boolean), _
                                            GetType(Window), New PropertyMetadata(True))

    Public Property DPIsReadOnly() As Boolean
        Get
            Return CBool(GetValue(DPIsReadOnlyProperty))
        End Get
        Set(ByVal value As Boolean)
            SetValue(DPIsReadOnlyProperty, value)
        End Set
    End Property   

依赖项属性声明为类型为 DependencyProperty 的共享 readonly 属性。该对象通过注册属性来创建。要注册您的属性,您需要提供一些参数。

第一个是您的属性名 stringDPIsReadOnly”。这是您的依赖项属性的名称,该名称应与您的变量定义相同,但在末尾省略“property”一词。

下一个参数是您的属性类型(Boolean)。

第三个是属性所有者的类型。我们的属性将是主窗口的依赖项,因此我们的所有者类型为“window”。

第四个参数是 PropertyMetaData 对象,该对象除了其他内容外,还将保存我们属性的默认值。它设置为 true,因此当我们的应用程序运行时,所有控件都设置为只读。如果我们将此值更改为 false,则在程序启动时所有控件都将是可编辑的。

在声明属性后,我们将其包装在一个标准的属性 get/set 声明中以访问它。不要在此属性中添加任何额外的代码到 getter 或 setter,因为它们不一定会执行。当 XAML 访问您的属性时,它会直接调用 getvaluesetvalue,从而绕过您的代码。您可以在有关依赖项属性的文档中了解更多信息,以及其他元数据选项。

绑定到菜单项

现在我们有了依赖项属性,我们可以将其绑定到菜单项,以将值从 true 切换到 false。由于我们在上面的 PropertyMetaData 对象中设置了默认值,因此在项目启动时它将是 true

这是我们主窗口中菜单项的 XAML

       <MenuItem Name="ToolsMenu"
                          Header="_Tools">
                    <MenuItem Header="_Lock Screen Input"
                              Command="{Binding Path=InactivateCommand}"
                              IsCheckable="True"
                              IsChecked="{Binding Path=DPIsReadOnly, 
                                        RelativeSource={RelativeSource
                                        Mode=FindAncestor, AncestorType=Window}, 
                                        Mode=TwoWay}">
                        <MenuItem.Icon>
                            <Image Source="/Images/Unlocked.png"
                                   Width="15"
                                   Height="15" />
                        </MenuItem.Icon>
                    </MenuItem></MenuItem>

IsCheckable 属性设置为 true,以便我们可以直观地看到属性的切换。

IsChecked 属性是我们绑定值到依赖项属性的地方。我们将路径设置为我们的自定义依赖项属性(DPIsReadOnly),并将相对源设置为搜索我们定义了依赖项属性的“window”。

IsChecked 属性将在项目启动时显示一个复选标记旁边的菜单项。(参见文章顶部的图片)。由于我们的依赖项属性设置为默认值 true,因此在项目启动时菜单项将处于选中状态。当值为 false 时,复选标记将被清除,从而允许我们显示菜单项图标。我在上面的 XAML 中将此图标设置为一个未锁定图像(Unlocked.png),以显示控件现在可以编辑。

将其应用于您的输入控件

我们目前有一个窗口的自定义依赖项属性和一个在 truefalse 之间切换此属性的机制。现在,我们需要将此属性附加到我们的文本框(或任何具有 readonly 属性的 UIElement)。我们通过在 usercontrol 的 XAML 中使用样式来完成最后一步。

<UserControl x:Class="UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:GlobalReadOnly">

    <UserControl.Resources>
        <Style x:Key="BaseStyle1"
               TargetType="TextBox">
            <Setter Property="IsReadOnly"
                    Value="{Binding Path=DPIsReadOnly, 
                             RelativeSource={RelativeSource
                             Mode=FindAncestor,
                             AncestorType=Window}}" />
        </Style>

        <Style x:Key="GridTextStyle1"
               TargetType="TextBox"
               BasedOn="{StaticResource BaseStyle1}">
            <Setter Property="Margin"
                    Value="40,10,40,10" />
            <Setter Property="TextAlignment"
                    Value="Center" />
            <Setter Property="Height"
                    Value="25" />
        </Style>
    </UserControl.Resources>

    <Grid>
        <ItemsControl ItemsSource="{Binding Path=BoxNames1}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Vertical"></StackPanel>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>

            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding .}" Style="{StaticResource GridTextStyle1}" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>

        </ItemsControl>
    </Grid>
</UserControl>

在这里,我们有几个选项。这都是正常的 WPF 和样式设置。我创建了一个“BaseStyle1”样式来将我们的依赖项属性值绑定到我们 textboxreadonly 属性。再次,我使用 relativesource 来搜索我们的“window”以查找自定义依赖项属性。

GridTextStyle1”样式中设置了文本框的其他属性。此样式使用 BasedOn 属性将此样式链接到实现到依赖项属性的 readonly 绑定的样式。

   <Style x:Key="GridTextStyle1"
               TargetType="TextBox"
               BasedOn="{StaticResource BaseStyle1}">

itemscontrol 中的 textbox 被设置为此样式,并继承了 readonly binding

  <TextBox Text="{Binding .}" Style="{StaticResource GridTextStyle1}" /> 

这使得许多样式可以基于 basestyle。您可以将所有这些逻辑放在一个样式中。您也可以省略基样式和在 textbox 上设置样式的键值,让该样式成为所有 textbox 的默认样式。

此样式也可以放在您的 application.xaml 资源中,以应用于应用程序中的所有 textbox。同样,您可以通过 WPF 允许的任何数量的方式应用您的 readonly 样式。

更改用户控件时重置值

主窗口底部的按钮通过切换 viewmodel 来更改显示,从而根据 mainresources.xaml 文件中的 datatemplate 更改显示的 usercontrol(参见详细代码文件)。

为了简化此程序,我将一个事件处理程序附加到了按钮的单击事件。当事件触发时,由于我们已经为自定义依赖项属性设置了 getter 和 setter,因此我们只需要在简单的属性赋值中设置其值。这也将切换菜单项的值。

  'This Resets the property when the view changes
    'This can also be replaced by a messenger system from the viewmodel.
    Private Sub Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        DPIsReadOnly = True
    End Sub  

要消除此代码后台,您还可以使用消息传递系统从 viewmodel 发送消息以重置 readonly 状态,并让主窗口“Listen”该消息来更改属性。

在不需要时禁用菜单

当切换到不使用只读属性的用户控件屏幕时,最好禁用该菜单项。为了禁用菜单项,我使用了标准的 Relay Command 结构,这是大多数 MVVM 编程的基础。菜单项绑定到 viewmodel 上的命令,并且其 CanExecute 属性设置为 true 以启用,设置为 false 以禁用。这是通过使用Josh Smith 的 Relay Command 来实现的。(请参阅 Josh Smith 的文章,其中对他的 Relay Command 进行了出色的解释)。

                      <MenuItem Header="_Lock Screen Input"
                              Command="{Binding Path=InactivateCommand}"
                              IsCheckable="True"
                              IsChecked="{Binding Path=DPIsReadOnly, 
                                        RelativeSource={RelativeSource
                                        Mode=FindAncestor, AncestorType=Window}, 
                                        Mode=TwoWay}">  

viewmodel 将在其构造函数中设置其值

       MyBase.ReadOnlyMenuIsActive = False 

并且基类 viewmodelcommandviewmodel”将根据 CanInactivate 函数的返回值来禁用菜单项。

    Public ReadOnly Property InactivateCommand() As ICommand
        Get
            Return New RelayCommand(AddressOf Me.Inactivate, AddressOf Me.CanInactivate)
        End Get
    End Property
    Public Function CanInactivate() As Boolean
        Return ReadOnlyMenuIsActive
    End Function
    Private Sub Inactivate()
    End Sub

    Private m_ReadOnlyMenuIsActive As Boolean
    Public Property ReadOnlyMenuIsActive() As Boolean
        Get
            Return m_ReadOnlyMenuIsActive
        End Get
        Set(ByVal value As Boolean)
            m_ReadOnlyMenuIsActive = value
            OnPropertyChanged(New PropertyChangedEventArgs("ReadOnlyMenuIsActive"))
        End Set
    End Property    

代码解释 - 解决方案 2

第二个示例将展示如何使用自定义的附加依赖项属性来实现相同的结果。大部分代码是相同的,但我将重点介绍不同的部分。为了展示如何将附加依赖项属性添加到任何元素,我们将它附加到主窗口的 Grid 而不是窗口本身。

创建自定义附加依赖项属性

首先,我们需要定义我们的自定义附加依赖项属性。附加属性声明与依赖项属性类似,但使用 RegisterAttached 方法代替。

Namespace myapp
    Public Class MyAttachedProperty

        'Attached Dependency Property
        Public Shared ReadOnly ADPIsReadOnlyProperty As DependencyProperty = _
                        DependencyProperty.RegisterAttached( _
                        "ADPIsReadOnly", _
                        GetType(Boolean), GetType(UIElement), ADPFlags)

        'You can set the metadata based on your needs
        Shared ADPFlags As FrameworkPropertyMetadata = New FrameworkPropertyMetadata(True, _
                    (FrameworkPropertyMetadataOptions.BindsTwoWayByDefault Or _
                     FrameworkPropertyMetadataOptions.Inherits), _
                    New PropertyChangedCallback(AddressOf ADPIsReadOnlyChanged))

        Public Shared Sub SetADPIsReadOnly(ByVal element As UIElement, ByVal value As Boolean)
            element.SetValue(ADPIsReadOnlyProperty, value)
        End Sub

        Public Shared Function GetADPIsReadOnly(ByVal element As UIElement) As Boolean
            Return DirectCast(element.GetValue(ADPIsReadOnlyProperty), Boolean)
        End Function

        'Need this call to change the property value in code.
        Private Shared Sub ADPIsReadOnlyChanged(ByVal sender As DependencyObject, _
                                                ByVal args As DependencyPropertyChangedEventArgs)
            sender.SetValue(ADPIsReadOnlyProperty, args.NewValue)
        End Sub

    End Class

End Namespace

由于附加属性可以设置在任何依赖项对象上,因此我们不创建相同的属性 getter/setter。相反,您需要创建一对共享方法来获取和设置属性值。共享方法的名称应为 GetYourPropertyNameSetYourPropertyName

RegisterAttached 方法需要提供几个参数。

第一个是您的属性名 stringADPIsReadOnly”。这是您的附加依赖项属性的名称,该名称应与您的变量定义相同,但在末尾省略“property”一词。

下一个参数是您的属性类型(Boolean)。

第三个是属性所有者的类型。我们希望能够将此属性用于任何控件,因此我们的所有者类型设置为“UIElement”。

第四个参数(可选)是 FrameworkPropertyMetadata 对象。此对象可以保存有关您的属性的几条信息。这里,除了设置默认值外,我还设置了一些属性来演示如何应用此对象。有关属性的完整列表,可以在有关附加依赖项属性的文档中找到,以及其他的 FrameworkPropertyMetadata 选项。

附加属性

现在我们有了附加依赖项属性,我们需要将其“附加”到主窗口中的一个依赖项对象。首先,我们必须在主窗口中添加一个命名空间引用,以便找到我们的附加依赖项属性类。

xmlns:ap="clr-namespace:GlobalReadOnly.myapp">   

然后,我们可以简单地将我们的属性添加到 Grid 定义中并为其分配所需的值。

 <Grid Name="Apgrid"
        ap:MyAttachedProperty.ADPIsReadOnly="True"
        Background="Orange">  

请注意,已向 Grid 添加了 name 值。在下面检索此元素以重置其值时需要此值。

绑定到菜单项

我们到菜单项的绑定与之前相同,只是因为我们将属性附加到了主窗口的 Grid,所以我们需要将 ancestor 类型更改为“Grid”。

 <MenuItem Name="ToolsMenu"
                          Header="_Tools">
                    <MenuItem Header="_Lock Screen Input"
                              Name="LockScreen"
                              Command="{Binding Path=InactivateCommand}"
                              IsCheckable="True"
                              IsChecked="{Binding Path=ADPIsReadOnly,
                                RelativeSource={RelativeSource Mode=FindAncestor,
                                AncestorType=Grid},
                                        Mode=TwoWay}">
                      
                        <MenuItem.Icon>
                            <Image Source="/Images/Unlocked.png"
                                   Width="15"
                                   Height="15" />
                        </MenuItem.Icon>
                    </MenuItem>
                </MenuItem>

将其应用于您的输入控件

样式也与之前相同,只是 relativesource 用于我们的 binding。我们需要将 Ancestor 类型更改为“Grid”,就像我们上面在菜单项绑定中所做的那样。

我们应用此样式的 textbox 位于 usercontrol 的自己 grid 中。为了让我们的绑定找到主窗口上的 Grid 而不是,我们需要将“AncestorLevel”设置为 2,这样它就会绕过 usercontrol 上的 Grid。

  <Style x:Key="BaseStyle2"
               TargetType="TextBox">
           <Setter Property="IsReadOnly"
                    Value="{Binding Path=ADPIsReadOnly, 
                             RelativeSource={RelativeSource
                             Mode=FindAncestor,
                             AncestorType=Grid, AncestorLevel=2}}" />
        </Style> 

同样,您可以通过 WPF 允许的任何数量的方式应用您的 readonly 样式。

更改用户控件时重置值

要重置附加依赖项属性的值,我们需要一个对包含它的对象的引用。这就是为什么我们在主窗口的 Grid 中添加“Name”属性的原因。我们现在可以使用此名称并设置附加依赖项属性的值。

        myapp.MyAttachedProperty.SetADPIsReadOnly(Apgrid, True)   

Using the Code

application.xaml.vb 文件(在源代码中),第一行包含一个常量“startwindow”。如果设置为 1,它将显示 Dependency Property demo 的主窗口。将此常量更改为 2,将显示 Attached Dependency Property demo 的主窗口。

    Friend Const startWindow As Integer = 1
© . All rights reserved.