在WPF中使用户控件可访问

3
我有一个WPF表单,由两列网格组成。
左侧列是控件标签,右侧列是我的控件。
这些控件都是UserControl。在最简单的情况下,其中一些控件仅包装现有的WPF控件,例如文本框,以便它们都实现公共接口。
当生成表单时,我有如下代码来设置关联控件的标签,其中newControl是创建的UserControl,ctl.Caption只是返回所需的标签文本:
Label newLabel = new Label();
newLabel.Content = ctl.Caption + ":";
newLabel.Target = newControl;

一个问题是设置目标实际上不起作用。如果标题中有下划线,助记键不会将焦点设置到包装的控件上。其中一个解决方法可能是在UserControl代码中手动将焦点设置到包装的控件上 - 但是...

最大的问题是可访问性。屏幕阅读器(例如JAWS和Windows内置Narrator)在控件获得焦点时不会读取控件标题。

我看过这个: http://msdn.microsoft.com/en-us/library/windows/desktop/gg712258.aspx - 它提供了很多细节,但没有有用的例子。它有很多关于自定义控件的内容,这对于一个简单的用户控件来说肯定是过度设计了吧?

那么,我应该如何正确地“连接”我的标签到我的UserControls上呢?

您可以浏览整个项目的代码,网址为http://quest.codeplex.com/SourceControl/changeset/view/676933506953,特定的代码位于EditorControls项目中,并且UserControls在ElementEditor.xaml.cs中实例化。


我会使用装饰器模式,因为它允许您在XAML中“包装”控件(即不必创建自定义控件)。今天下班后我会发布一个示例。 - Dennis
2个回答

1

你的 newControl 是 Control 类型的,它不允许你添加额外的内容。如果你想要添加一些内容,你需要使用支持此功能的类,比如 ContentControl 或 Panel(用于多个子元素),或者你可以实现一个实现 IAddChild 接口的自定义控件。

对于你的问题,一个简单的解决方案可能是:

<UserControl x:Class="UIMocks.MyCustomControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <StackPanel x:Name="content"/>
</UserControl>

代码后台

[ContentProperty("Children")]
public partial class MyCustomControl : UserControl
{
    public MyCustomControl()
    {
        InitializeComponent();
    }

    public UIElementCollection Children { get { return content.Children; } }
}

然后你就可以使用

MyCustomControl newControl = InitialiseEditorControl(ctl);
...
Label newLabel = new Label();
newLabel.Content = ctl.Caption + ":";
newControl.Children.Add(newLabel);

AddChild在ContentControl中是受保护的,因此无法在我的AddControlToGrid函数中访问它。而UserControl不能转换为Panel。此外,从文档中可以看出IAddChild在.net 4中已被弃用。 - Alex Warren
你说得对,IAddChild现在已经过时了,它已被ContentProperty属性所取代。感谢你指出这一点。 我并不是在说他应该将他的用户控件转换为Panel,只是在说明他可以使用一个具有像Panel一样内容的控件(Panel是抽象的,不能直接使用)。 - Marco Cordeiro

0

嗯,我尝试在一个小的测试项目中重现您的问题,但对我来说它是有效的...所以我猜您需要提供更多有关如何构建您的用户控件的细节。这是对我有效的方法:

我创建了一个空项目(只有应用程序和窗口文件,像往常一样),并在我的窗口中设置了一个具有2列的网格:

<Window x:Class="Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Name="Window"
        SizeToContent="WidthAndHeight">

    <Grid Name="MyGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>
    </Grid>

</Window>

然后创建了一个扩展WPF TextBox类的userControl:

<TextBox x:Class="Test.MyTextBox"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">

</TextBox>

以及:

使用 System.Windows; 使用 System.Windows.Controls;

namespace Test
{
    public partial class MyTextBox : TextBox
    {
        public static readonly DependencyProperty CaptionProperty =
             DependencyProperty.Register("Caption", typeof(string), typeof(MyTextBox), new UIPropertyMetadata(""));
        public string Caption
        {
            get { return (string)GetValue(CaptionProperty); }
            set { SetValue(CaptionProperty, value); }
        }

        public MyTextBox()
        {
            InitializeComponent();
        }
    }
}

这基本上是一个带有“标题”dp的文本框。

现在在我的窗口代码后面:

public MainWindow()
{
    InitializeComponent();

    MyTextBox tb = new MyTextBox { Caption = "_Foo", Width = 100 };
    Label lb = new Label { Content = tb.Caption + ":", Target = tb };

    MyGrid.Children.Add(lb);
    MyGrid.Children.Add(tb);

    Grid.SetColumn(lb, 0);
    Grid.SetColumn(tb, 1);
}

通过这种方式,当我按下ALT + F时,我确实会将焦点放在TB上(甚至可以在标签中仅按ALT时看到“Foo”的F下面的_)

所以我想你的问题与你的用户控件本身以及它们是如何构建的有关(例如使用了哪个模板)

编辑:

如果您的控件不是扩展现有控件而是包含WPF控件,则问题可能出在Focus方法上。您应该添加一个Focus()方法,在控件本身获得焦点时将焦点设置在控件的正确部分。

代码(用于包含要获取焦点的文本框的用户控件):

<TextBox x:Class="Test.MyTextBox"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Button Content="foo" Grid.Column="0" />
        <TextBox Name="TextBoxPart" Grid.Column="1" />
    </Grid> 

</TextBox>

代码后台

public partial class MyTextBox : TextBox
{
    public static readonly DependencyProperty CaptionProperty =
         DependencyProperty.Register("Caption", typeof(string), typeof(MyTextBox), new UIPropertyMetadata(""));
    public string Caption
    {
        get { return (string)GetValue(CaptionProperty); }
        set { SetValue(CaptionProperty, value); }
    }

    public MyTextBox()
    {
        InitializeComponent();
    }

    protected override void OnGotFocus(RoutedEventArgs e)
    {
        TextBoxPart.Focus();
    }
}

编辑2: 我曾经遇到一个问题,需要将焦点转移到数据网格单元格中的子控件,这是我在模板中所做的:

 <ControlTemplate.Triggers>
     <Trigger Property="IsFocused" Value="True">
          <Setter TargetName="TextBoxPart" Property="FocusManager.FocusedElement" Value="{Binding ElementName=TextBoxPart}" />
     </Trigger>
 </ControlTemplate.Triggers>

你可以尝试将这段代码添加到你的模板中。这应该能够正确地转移你的焦点。

至于可访问性,我不认为这会有所帮助,但我也没有看到任何实现你想要的方式的方法 :-/


在我的情况下,我没有扩展任何现有的控件,只是将它们包装起来。因此,也许我有一些名为MyCustomTextBoxControl的控件,它可能包含一个文本框,但并不继承自它(也许是因为它旁边还有一个按钮或其他东西)。 - Alex Warren
谢谢David,我刚试了一下,但好像没有任何效果。即使这样,可访问性仍然存在问题——当使用Narrator或JAWS时,将焦点放在文本框上仍无法读出标签标题。 - Alex Warren
糟糕...我明天会进一步考虑你的问题。 - David
谢谢David。如果有帮助的话(可能过度),您可以从http://quest.codeplex.com下载整个源代码 - 问题代码位于EditorControls项目中,并且UserControls在ElementEditor.xaml.cs中实例化。 - Alex Warren
我终于找到了我要寻找的那段代码,请看我的第二次编辑。希望这能解决你至少一半的问题。至于剩下的... - David

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接