[英]Click-to-Edit Control LostFocus event issue
我正在研究一个简单的自定义控件,应通过双击将其转到“编辑”模式
该概念基于此问题,Silverlight中的点击编辑
双击它可以更改“编辑模板”上的初始模板,除了(5)如何更改模板外,它似乎很清楚。当控件失去焦点时,如何返回模板仅当包含的控件失去焦点时才触发“丢失焦点”事件这是一篇谈论它的文章http://programmerpayback.com/2008/11/20/gotfocus-and-lostfocus-events-on-containers/
我尝试实现相同的技术,但仍然没有结果,当我在控件外部单击时,我无法获得LostFocus事件
我的问题在哪里?
我的XAML
<ContentControl x:Class="Splan_RiaBusinessApplication.Controls.TimeCodeControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:obj="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:behaviour="clr-namespace:Splan_RiaBusinessApplication.Behavior" xmlns:controls="clr-namespace:Splan_RiaBusinessApplication.Controls" xmlns:Primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data" mc:Ignorable="d" IsTabStop="True" IsEnabled="True" Visibility="Visible" d:DesignHeight="100" d:DesignWidth="200" d:Height="200" d:Width="200" > <ContentControl.Resources> <ControlTemplate x:Key="DisplayTemplate"> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding Target.Code, Mode=TwoWay}" /> <StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" > <TextBlock Text="{Binding Target.Start, Mode=TwoWay, StringFormat=hh\\\\:mm }" /> <TextBlock Text='-' /> <TextBlock Text="{Binding Target.End, Mode=TwoWay, StringFormat=hh\\\\:mm }" /> </StackPanel> </Grid> </ControlTemplate> <ControlTemplate x:Key="EditTemplate"> <Grid Background="Aqua" Height="200" Width="200"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <ComboBox Width="100" Height="25" x:Name="cbTimeCode" ItemsSource="{Binding TimeCodes}" SelectedValue="{Binding Target.CodeId, Mode=TwoWay}" SelectedValuePath="TimeCodeId" > <ComboBox.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="40"/> <ColumnDefinition Width="100"/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Target.Code}" /> <TextBlock Grid.Column="1" Text="{Binding Target.Description}" /> </Grid> </DataTemplate> </ComboBox.ItemTemplate> <i:Interaction.Triggers> <i:EventTrigger> <behaviour:ResolveElementName PropertyName="ItemsSource" /> </i:EventTrigger> </i:Interaction.Triggers> </ComboBox> <!--<controls:TimeRangePickerControl Grid.Row="1" StartTime="{Binding Target.Start, Mode=TwoWay}" EndTime="{Binding Target.End, Mode=TwoWay}"/>--> </Grid> </ControlTemplate> </ContentControl.Resources> <Grid x:Name="Layout" Background="Aquamarine"> <ItemsControl x:Name="PlaceHolder" Template="{StaticResource DisplayTemplate}"> </ItemsControl> </Grid> </ContentControl>
背后的代码
using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; namespace Splan_RiaBusinessApplication.Controls { public class TimeCode { public int TimeCodeId {get;set;} public string Code { get; set; } public string Description { get; set; } } public class TimeDetail { public int TimeDetailId { get;set; } public int CodeId { get;set;} public TimeSpan Start { get; set; } public TimeSpan End { get; set; } public string Code { get; set; } public string Comment { get; set; } } public partial class TimeCodeControl : ContentControl { public class TimeCodeControlEventArgs : EventArgs { public string userName { get; set; } } private static TimeSpan DoubleClickThreshold = TimeSpan.FromMilliseconds(300); private DateTime _lastClick; private Boolean m_EditMode = false; public Boolean EditMode { get { return m_EditMode; } set { if (m_EditMode != value) { switch (value) { case false: PlaceHolder.Template = this.Resources["DisplayTemplate"] as ControlTemplate; break; case true: PlaceHolder.Template = this.Resources["EditTemplate"] as ControlTemplate; break; } m_EditMode = value; } } } public bool IsFocused { get { return FocusManager.GetFocusedElement() == this; } } public TimeCodeControl() { TimeCodes = new List<TimeCode>() { new TimeCode { TimeCodeId = 200, Code= "C", Description="Cash" } }; InitializeComponent(); Layout.DataContext = this; this.IsTabStop = true; this.Visibility = Visibility.Visible; this.IsEnabled = true; this.Focus(); Layout.MouseLeftButtonDown += Layout_MouseLeftButtonDown; //Layout.KeyDown += Layout_KeyDown; //Layout.KeyUp += Layout_KeyUp; this.LostFocus += TimeCodeControl_LostFocus; this.GotFocus += TimeCodeControl_GotFocus; } void TimeCodeControl_GotFocus(object sender, RoutedEventArgs e) { } void TimeCodeControl_LostFocus(object sender, RoutedEventArgs e) { } public TimeDetail Source { get { return (TimeDetail)GetValue(SourceProperty); } set { SetValue(SourceProperty, value); } } public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(TimeDetail), typeof(TimeCodeControl), new PropertyMetadata(SourceChanged)); private static void SourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { var control = sender as TimeCodeControl; if (control == null) return; control.Target = control.Source; //.Copy(); } public List<TimeCode> TimeCodes { get; set; } public TimeDetail Target { get; set; } private bool FocusIsInside(object parent) { bool rs = false; dynamic oFocus = FocusManager.GetFocusedElement(); while (oFocus != null) try { if ((oFocus.GetType() == parent.GetType()) && (oFocus.Equals(this))) { rs = true; break; } else { oFocus = oFocus.Parent; } } catch { break; } return rs; } private Boolean hasFocus = false; protected override void OnGotFocus(RoutedEventArgs e) { base.OnGotFocus(e); if (!hasFocus) { hasFocus = true; Debug.WriteLine("Container Got Focus"); } } protected override void OnLostFocus(RoutedEventArgs e) { base.OnLostFocus(e); //if (!FocusIsInside(Layout)) //{ // hasFocus = false; // Debug.WriteLine("Container Lost Focus"); // EditMode = false; //} } void Layout_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (DateTime.Now - this._lastClick <= DoubleClickThreshold) { EditMode = true; this._lastClick = DateTime.Now; e.Handled = true; return; } this._lastClick = DateTime.Now; } } }
更新:我决定利用计时器来识别用户从容器外部带来焦点或只是将焦点从一个控件切换到容器内部的另一个场景。 它可能不是最好的解决方案,但似乎目前正在奏效。 我将不胜感激有关不同方法或实现的任何建议。
public partial class MyControl: ContentControl { ... public event EventHandler<RoutedEventArgs> LostFocus; public event EventHandler<RoutedEventArgs> GotFocus; bool Focused = false; DispatcherTimer FocusTimer = null; protected override void OnGotFocus(RoutedEventArgs e) { base.OnGotFocus(e); if (Focused) return; Focused = true; // it focused from the outside of the control // becouse the timer wasn't initialised on the previous LostFocused event // generated by other control in the same ContentControl contaner if (FocusTimer == null) { if (GotFocus != null) GotFocus(e.OriginalSource, e); Debug.WriteLine("Got Focus "); return; } // It was switched from one hosted control to another one FocusTimer.Stop(); FocusTimer = null; } protected override void OnLostFocus(RoutedEventArgs e) { base.OnLostFocus(e); if (e.OriginalSource is ComboBox && FocusManager.GetFocusedElement() is ComboBoxItem) return; FocusTimer = new DispatcherTimer(); Focused = false; FocusTimer.Interval = new TimeSpan(0, 0, 0, 0, 50); FocusTimer.Tick += (s, args) => { FocusTimer.Stop(); FocusTimer = null; // if after the timeout the focus still not triggered // by another contained element // the We lost a focus on the container if (!Focused ) { if(LostFocus != null) LostFocus(e.OriginalSource, e); Debug.WriteLine("Lost Focus " ); } }; FocusTimer.Start(); } ... }
有几个问题。 让我们来看看...
当您在控件外部单击时,为什么没有收到LostFocus事件?
好吧,我也早在这个错误假设的受害者。 除非您单击一个明确将焦点设置为点击的控件(如TextBox或各种Button),否则外部单击不会改变焦点。 按Tab键将键盘焦点导航到下一个控件,然后查看是否引发了该事件。
但是,让我们谈谈其他问题:
ControlTemplate x:Key="DisplayTemplate"
和ControlTemplate x:Key="EditTemplate"
不建议以这种方式使用ControlTemplates
。 而是使用DataTemplate
和相应的ContentPresenters
。
TimeCodeControl : ContentControl
和x:Class="Splan_RiaBusinessApplication.Controls.TimeCodeControl"
是的,我知道这是可能的,但并不是真正有用。 让我解释一下:您可以编写自己的专用“单击即编辑”控件作为一站式工具:具有硬编码的DisplayTemplate和EditTemplate来编辑TimeCode
和TimeDetail
数据(基本上是您所做的)。 但是,您再也没有机会使用它并指定另一对模板以允许编辑其他数据类型。 因此,从ContentControl派生没有多大意义,也可以从UserControl派生。
一种替代方法是:将Click-To-Edit控件编写为可重复使用的常规控件,该控件提供两个公共属性:DisplayTemplate和EditTemplate。 并且不要对您的DataContext做任何假设。 同样,将ContentControl作为父类也没有任何好处。 我建议您从Control
派生,添加两个前面提到的DataTemplate
类型的DependencyProperties
,定义一个默认ControlTemplate,其中包含一个或两个ContentPresenters。 在您的控制代码中,您需要处理MouseLeftButtonDown和LostFocus并相应地更新布尔标志。
这是一个工作示例:
...确定焦点的扩展方法:
public static class ControlExtensions
{
public static bool IsFocused( this UIElement control )
{
DependencyObject parent;
for (DependencyObject potentialSubControl = FocusManager.GetFocusedElement() as DependencyObject; potentialSubControl != null; potentialSubControl = parent)
{
if (object.ReferenceEquals( potentialSubControl, control ))
{
return true;
}
parent = VisualTreeHelper.GetParent( potentialSubControl );
if (parent == null)
{
FrameworkElement element = potentialSubControl as FrameworkElement;
if (element != null)
{
parent = element.Parent;
}
}
}
return false;
}
}
...和一个不错的自定义控件:
public class ClickToEditControl : Control
{
public ClickToEditControl()
{
DefaultStyleKey = typeof (ClickToEditControl);
MouseLeftButtonDown += OnMouseLeftButtonDown;
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount==2)
{
GotoEditMode();
e.Handled = true;
}
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if (!this.IsFocused())
GotoDisplayMode();
}
private void GotoDisplayMode()
{
IsInEditMode = false;
}
private void GotoEditMode()
{
IsInEditMode = true;
}
public DataTemplate EditTemplate
{
get { return (DataTemplate) GetValue( EditTemplateProperty ); }
set { SetValue( EditTemplateProperty, value ); }
}
public static readonly DependencyProperty EditTemplateProperty =
DependencyProperty.Register( "EditTemplate", typeof( DataTemplate ), typeof( ClickToEditControl ), null );
public DataTemplate DisplayTemplate
{
get { return (DataTemplate) GetValue( DisplayTemplateProperty ); }
set { SetValue( DisplayTemplateProperty, value ); }
}
public static readonly DependencyProperty DisplayTemplateProperty =
DependencyProperty.Register( "DisplayTemplate", typeof( DataTemplate ), typeof( ClickToEditControl ), null );
public bool IsInEditMode
{
get { return (bool) GetValue( IsInEditModeProperty ); }
set { SetValue( IsInEditModeProperty, value ); }
}
public static readonly DependencyProperty IsInEditModeProperty =
DependencyProperty.Register( "IsInEditMode", typeof( bool ), typeof( ClickToEditControl ), null );
}
...和ControlTemplate:
<clickToEdit:BoolToVisibilityConverter x:Key="VisibleIfInEditMode"/>
<clickToEdit:BoolToVisibilityConverter x:Key="CollapsedIfInEditMode" VisibleIfTrue="False"/>
<Style TargetType="clickToEdit:ClickToEditControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="clickToEdit:ClickToEditControl">
<Grid>
<ContentPresenter
ContentTemplate="{TemplateBinding EditTemplate}"
Content="{Binding}"
Visibility="{Binding IsInEditMode, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource VisibleIfInEditMode}}"/>
<ContentPresenter
ContentTemplate="{TemplateBinding DisplayTemplate}"
Content="{Binding}"
Visibility="{Binding IsInEditMode, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource CollapsedIfInEditMode}}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
BoolToVisibilityConverter
public class BoolToVisibilityConverter : IValueConverter
{
public bool VisibleIfTrue { get; set; }
public BoolToVisibilityConverter(){VisibleIfTrue = true;}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (VisibleIfTrue)
return ((bool) value) ? Visibility.Visible : Visibility.Collapsed;
else
return ((bool) value) ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){throw new NotSupportedException();}
}
用法:
<clickToEdit:ClickToEditControl Height="20" Width="200">
<clickToEdit:ClickToEditControl.DisplayTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyText}"/>
</DataTemplate>
</clickToEdit:ClickToEditControl.DisplayTemplate>
<clickToEdit:ClickToEditControl.EditTemplate>
<DataTemplate>
<TextBox Text="{Binding MyText, Mode=TwoWay}"/>
</DataTemplate>
</clickToEdit:ClickToEditControl.EditTemplate>
</clickToEdit:ClickToEditControl>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.