[英]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.