【问题标题】:Focusing element produced in a style trigger via ContentTemplate通过 ContentTemplate 在样式触发器中生成的焦点元素
【发布时间】:2012-03-10 14:24:35
【问题描述】:

使用 WPF 我正在尝试生成一个 TextBlock,它在双击后“转换”为准备好编辑的 TextBox。之后,Enter 键、Esc 键或失去焦点会导致编辑结束,并且 TextBox 会恢复为 TextBlock。

我找到的解决方案大部分都有效。我的问题是我无法将 TextBox 集中在“转换”上,从而迫使用户再次显式单击元素以将其聚焦并开始编辑。

代码说明

我选择实现此行为的方式是使用模板和样式的 DataTrigger 来更改元素的模板。在我展示的示例中,该元素是一个简单的 ContentControl,尽管我尝试执行此操作的实际用例稍微复杂一些(我有一个 ListBox,其中每个元素都可以通过此行为进行编辑,一次一个)。

思路如下:

  • ContentControl 具有关联的样式
  • 关联的 Style 将 ContentTemplate 定义为 TextBlock。
  • 模型对象有一个属性 InEditing,当我想编辑控件时该属性变为 true
  • 通过 TextBlock 上的 MouseBinding,我将模型的 InEditing 属性设置为 True
  • 样式有一个监听 InEditing 的 DataTrigger,在这种情况下将 ContentTemplate 设置为一个 TextBox
  • 通过 EventSetters,我捕获了 Enter、Esc 和 LostFocus,以便恢复保存更改并恢复到以前的样式。请注意:我不能直接将事件附加到 TextBox,否则我会得到一个 The event cannot be specified on a Target tag in a Style.请改用 EventSetter。

虽然不是最佳的(视图和模型行为有一定的混合 - 特别是在 InEditing 属性中 - 我不喜欢通过各种处理程序大幅重新实现对 TextBox 模型的更改的提交逻辑KeyDown 和 LostFocus),系统实际上可以正常工作。

失败的实现思路

一开始我想连接到TextBox的IsVisibleChanged事件,并在那里设置焦点。不能这样做,因为前面提到的 无法在 Style 中的 Target 标记上指定事件。请改用 EventSetter。

无法使用错误提示的解决方案,因为这样的事件不是路由事件,因此不能在EventSetter中使用。

代码

代码分为四个文件。

模型.cs:

using System.Windows;

namespace LeFocus
{
    public class Model: DependencyObject
    {
        public bool InEditing
        {
            get { return (bool)GetValue(InEditingProperty); }
            set { SetValue(InEditingProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InEditing.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InEditingProperty =
            DependencyProperty.Register("InEditing", typeof(bool), typeof(Model), new UIPropertyMetadata(false));


        public string Name
        {
            get { return (string)GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Name.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name", typeof(string), typeof(Model), new UIPropertyMetadata("Hello!"));
    }
}

App.xaml:

<Application x:Class="LeFocus.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:lefocus="clr-namespace:LeFocus"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <lefocus:Model x:Key="Model"/>
    </Application.Resources>
</Application>

MainWindow.xaml:

<Window x:Class="LeFocus.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:lefocus="clr-namespace:LeFocus"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding Source={StaticResource Model}}" 
        Name="mainWindow">
    <Window.Resources>
        <Style x:Key="SwitchingStyle"
               TargetType="{x:Type ContentControl}">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate DataType="{x:Type lefocus:Model}">
                        <TextBlock Text="{Binding Path=Name}">
                            <TextBlock.InputBindings>
                                <MouseBinding MouseAction="LeftDoubleClick"
                                              Command="lefocus:MainWindow.EditName"
                                              CommandParameter="{Binding}"/>
                            </TextBlock.InputBindings>
                        </TextBlock>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
            <EventSetter Event="TextBox.KeyDown" Handler="TextBox_KeyDown"/>
            <EventSetter Event="TextBox.LostFocus" Handler="TextBox_LostFocus"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=InEditing}" Value="True">
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate DataType="{x:Type lefocus:Model}">
                                <TextBox Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=lefocus:MainWindow, AncestorLevel=1}, Path=NameInEditing, UpdateSourceTrigger=PropertyChanged}" TextChanged="TextBox_TextChanged" KeyDown="TextBox_KeyDown_1" />
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Window.CommandBindings>
        <CommandBinding Command="lefocus:MainWindow.EditName" Executed="setInEditing"/>
    </Window.CommandBindings>
    <Grid>
        <ContentControl Style="{StaticResource SwitchingStyle}" Content="{Binding}"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace LeFocus
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }


        public string NameInEditing
        {
            get { return (string)GetValue(NameInEditingProperty); }
            set { SetValue(NameInEditingProperty, value); }
        }

        // Using a DependencyProperty as the backing store for NameInEditing.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NameInEditingProperty =
            DependencyProperty.Register("NameInEditing", typeof(string), typeof(MainWindow), new UIPropertyMetadata(null));


        public static readonly RoutedUICommand EditName =
            new RoutedUICommand("EditName", "EditName", typeof(MainWindow));


        private void setInEditing(object sender, ExecutedRoutedEventArgs e)
        {
            var model = ((Model)e.Parameter);
            NameInEditing = model.Name;
            model.InEditing = true;
        }


        private void TextBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                var model = getModelFromSender(sender);
                model.Name = NameInEditing;
                NameInEditing = null;
                model.InEditing = false;
            }
            else if (e.Key == Key.Escape)
            {
                var model = getModelFromSender(sender);
                model.InEditing = false;
            }
        }

        private void TextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            var model = getModelFromSender(sender);
            model.Name = NameInEditing;
            NameInEditing = null;
            model.InEditing = false;
        }

        private static Model getModelFromSender(object sender)
        {
            var contentControl = (ContentControl)sender;
            var model = (Model)contentControl.DataContext;
            return model;
        }
    }
}

【问题讨论】:

  • 我回答了一个类似的问题stackoverflow.com/questions/9438396/…。它可能会有所帮助。这个想法是设置文本框的样式,使其看起来像一个文本块,直到您选择它。
  • 这看起来很有希望;原始用户遇到的问题也与我的类似。我看到的唯一问题是我希望控件只能在双击时进行编辑(单击:选择;双击:编辑),而且我看不到使用这种样式获得它的简单方法。

标签: c# wpf textbox datatemplate routedevent


【解决方案1】:

可以使用此设置的一种方法是在TextBox 上处理Loaded,然后处理Keyboard.Focus 它(sender)。

【讨论】:

  • 似乎不起作用;它仅在程序启动时以 ContentControl 作为参数调用一次(奇怪:我认为将“TextBox.Loaded”指定为事件仅在 TextBox 上强制它)。
  • @RedGlow:我说的是 on TextBox(如TextChanged),不是样式,每次模板启动时都需要触发它。 Loaded 事件也存在于 ContentControl 上,因此在带有 "TextBox." 前缀的 EventSetter 中使用它没有任何作用。
【解决方案2】:

我认为Change listbox template on lost focus event in WPF 中的代码已经可以满足您的需求。这是一个隐藏列表框选择矩形的修改,以便行为更加明显。选择列表框项时(单击一次),文本框边框变为可见,但仅当鼠标悬停在其上且尚不可编辑时(尝试键入)。当您再次单击它时,或者首先双击选择该项目,然后可以对其进行编辑。

<Page.Resources>
    <ResourceDictionary>

        <Style x:Key="NullSelectionStyle" TargetType="ListBoxItem">
            <Style.Resources>
                <!-- SelectedItem with focus -->
                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
                <!-- SelectedItem without focus -->
                <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
                <!-- SelectedItem text foreground -->
                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="{DynamicResource {x:Static SystemColors.ControlTextColorKey}}" />
            </Style.Resources>
            <Setter Property="FocusVisualStyle" Value="{x:Null}" />
        </Style>

        <Style x:Key="ListBoxSelectableTextBox" TargetType="{x:Type TextBox}">
            <Setter Property="IsHitTestVisible" Value="False" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}, AncestorLevel=1}}" Value="True">
                    <Setter Property="IsHitTestVisible" Value="True" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ResourceDictionary>
</Page.Resources>
<Grid>
    <ListBox ItemsSource="{Binding Departments}" HorizontalContentAlignment="Stretch">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBox Margin="5" Style="{StaticResource ListBoxSelectableTextBox}" Text="{Binding Name}" BorderBrush="{x:Null}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-12
    相关资源
    最近更新 更多