如何防止构造函数在值更改时被调用
How To Prevent Constructor from being called on value change
我有一个在 DataTemplate 中使用的自定义用户控件。在这个用户控件中,我有一个文本框。以前,当我实现此用户控件时,我不会遇到任何问题,但现在我已将其放入 Datatemplate 中,似乎出了点问题。
每次我在文本框中输入内容时,代码运行正常,但在我的触发器结束时,用户控件构造函数被再次调用并清除我的文本框。 (隐藏的线程调用它,所以我什至不知道从哪里开始寻找这个 unintuitive 调用的来源)
我正在尝试找出导致此构造函数再次触发的原因。其他绑定似乎运行良好,并且正在填充和显示正确的信息。只是在所有内容解析并清除 ui 控件的内部变量后再次调用的构造函数。
当前执行:
我在文本框中输入。触发器获取我的文本框值相应地过滤列表,然后调用构造函数并将文本框重置为默认值“”。
期望执行:
我在文本框中输入。触发器获取我的文本框值相应地过滤列表。
<UserControl x:Class="Analytics_Module.Views.TenantProfileFilterFieldsView"
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"
xmlns:local="clr-namespace:Analytics_Module.Views"
xmlns:vm="clr-namespace:Analytics_Module.ViewModels"
xmlns:uiComponents="clr-namespace:Analytics_Module.UI_Components"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<DockPanel>
<DockPanel.DataContext>
<vm:TenantProfileFilterFieldsViewModel x:Name="test"/>
</DockPanel.DataContext>
.....
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding FiltersState.GroupedTenantNames, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<uiComponents:neoCombobox
LabelText="Tenant Names"
ListBoxItems="{Binding StaticLists.TenantNames, ElementName=test}"
DisplayListBoxItems ="{Binding}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
自定义用户控制
<UserControl x:Class="Analytics_Module.UI_Components.neoCombobox"
x:Name="parent"
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"
xmlns:model="clr-namespace:Analytics_Module.Models"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel DataContext="{Binding ElementName=parent}" Width="200">
<Label Name="ComboboxLabel"
Content="{Binding Path=LabelText, FallbackValue='Error'}" Margin="5"/>
<TextBox Name="InputField"
Text="{Binding Path=TextBoxValue, Mode=TwoWay, FallbackValue='Error', UpdateSourceTrigger='PropertyChanged'}"/>
<!--TODO rename -->
<ListBox Name="Something"
ItemsSource="{Binding Path=DisplayListBoxItems, FallbackValue={}, Mode=TwoWay}" >
<ListBox.ItemTemplate >
<DataTemplate >
<StackPanel>
<CheckBox Margin="-1"
Content="{Binding Name, FallbackValue='Error'}"
IsChecked="{Binding Check_Status, Mode=TwoWay, FallbackValue=true}">
</CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</UserControl>
用户控制后端
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Analytics_Module.Models;
using Analytics_Module.Utillity;
using System.Timers;
using System.Collections.Specialized;
namespace Analytics_Module.UI_Components
{
/// <summary>
/// Interaction logic for neoCombobox.xaml
/// </summary>
public partial class neoCombobox : UserControl
{
#region LabelText DP
public String LabelText
{
get { return (String)GetValue(LabelTextProperty); }
set { SetValue(LabelTextProperty, value); }
}
public static readonly DependencyProperty LabelTextProperty =
DependencyProperty.Register("LabelText",
typeof(string),
typeof(neoCombobox), new PropertyMetadata("")
);
#endregion
#region TextBoxValue DP
/// <summary>
/// Gets or sets the Value which is being displayed
/// </summary>
public String TextBoxValue
{
get { return (String)GetValue(TextBoxValueProperty); }
set { SetValue(TextBoxValueProperty, value); }
}
/// <summary>
/// Identified the TextBoxValue dependency property
/// </summary>
public static readonly DependencyProperty TextBoxValueProperty =
DependencyProperty.Register("TextBoxValue",
typeof(String),
typeof(neoCombobox),
new PropertyMetadata("")
);
#endregion
#region ListBoxItems DP
public ItemsChangeObservableCollection<MultiSelectDropDownListEntry> ListBoxItems
{
get { return (ItemsChangeObservableCollection<MultiSelectDropDownListEntry>)GetValue(ListBoxItemsProperty); }
set { SetValue(ListBoxItemsProperty, value); }
}
public static readonly DependencyProperty ListBoxItemsProperty =
DependencyProperty.Register("ListBoxItems",
typeof(ItemsChangeObservableCollection<MultiSelectDropDownListEntry>),
typeof(neoCombobox),
new PropertyMetadata(new ItemsChangeObservableCollection<MultiSelectDropDownListEntry>())
);
#endregion
#region DisplayListBoxItems DP
public ItemsChangeObservableCollection<MultiSelectDropDownListEntry> DisplayListBoxItems
{
get {
if (GetValue(DisplayListBoxItemsProperty) == null)
{
SetValue(DisplayListBoxItemsProperty, new ItemsChangeObservableCollection<MultiSelectDropDownListEntry>());
}
return (ItemsChangeObservableCollection<MultiSelectDropDownListEntry>)GetValue(DisplayListBoxItemsProperty);
}
set { SetValue(DisplayListBoxItemsProperty, value); }
}
public static readonly DependencyProperty DisplayListBoxItemsProperty =
DependencyProperty.Register("DisplayListBoxItems",
typeof(ItemsChangeObservableCollection<MultiSelectDropDownListEntry>),
typeof(neoCombobox),
new PropertyMetadata(new ItemsChangeObservableCollection<MultiSelectDropDownListEntry>())
);
#endregion
/// <summary>
/// _timer is used to determine if a user has stopped typing.
/// The timer is started when a user starts typing again or
/// types for the first time.
/// </summary>
private readonly Timer _timerKeyPress;
/// <summary>
/// _timer is used to determine if a user has left the typing.
/// The timer is started when a user starts typing again or
/// types for the first time.
/// </summary>
private readonly Timer _timerMouseLeave;
public neoCombobox()
{
if (TextBoxValue != "") return;
InitializeComponent();
_timerKeyPress = new Timer();
_timerKeyPress.Interval = 750;
_timerKeyPress.Elapsed += new ElapsedEventHandler(UserPausedTyping);
_timerKeyPress.AutoReset = false;
_timerMouseLeave = new Timer();
_timerMouseLeave.Interval = 550;
//_timerMouseLeave.Elapsed += new ElapsedEventHandler(UserLeft);
_timerMouseLeave.AutoReset = false;
}
//TODO Add property to determine if user preferes Mouse Leave of focus leave.
protected override void OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
Console.WriteLine("@@@@@@@@@@@@@@@OnPreviewGotKeyboardFocus");
_timerMouseLeave.Stop();
base.OnPreviewGotKeyboardFocus(e);
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
Console.WriteLine("------------OnPreviewLostKeyboardFocus");
_timerMouseLeave.Stop();
_timerMouseLeave.Start();
base.OnPreviewLostKeyboardFocus(e);
}
protected override void OnMouseEnter(MouseEventArgs e)
{
_timerMouseLeave.Stop();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(MouseEventArgs e)
{
_timerMouseLeave.Stop();
_timerMouseLeave.Start();
base.OnMouseLeave(e);
}
protected override void OnKeyUp(KeyEventArgs e)
{
_timerKeyPress.Stop();
_timerKeyPress.Start();
}
private void UserPausedTyping(object source, ElapsedEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
Console.WriteLine("@@@@@@@@@@@@@@@UserPausedTyping");
this.RefreshDisplayList();
});
}
private void UserLeft(object source, ElapsedEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
Console.WriteLine("@@@@@@@@@@@@@@@User Left");
this.TextBoxValue = "";
this.RefreshDisplayList();
});
}
protected void RefreshDisplayList()
{
int ItemsourceCount = 0;
foreach (MultiSelectDropDownListEntry entry in this.DisplayListBoxItems.ToList())
{
if (!entry.Check_Status) this.DisplayListBoxItems.Remove(entry);
}
if (this.TextBoxValue == "") return;
foreach (MultiSelectDropDownListEntry entry in this.ListBoxItems)
{
if (entry.Name.ToString().ToLower().Contains(this.TextBoxValue.ToLower()) && !this.DisplayListBoxItems.Contains(entry))
{
this.DisplayListBoxItems.Add(entry);
if (ItemsourceCount++ > 15) break;
}
}
}
}
}
您不能总是避免由 ItemsControl
重新创建容器,但是您可以通过将 TextBox
的 Text
属性 绑定到视图模型的属性,而不是自定义控件本身的属性。
而不是写作:
<TextBox Name="InputField" Text="{Binding Path=TextBoxValue, Mode=TwoWay, FallbackValue='Error', UpdateSourceTrigger='PropertyChanged'}"/>
也许写
<TextBox Name="InputField" Text="{Binding MyTextProperty, Mode=TwoWay, FallbackValue='Error', UpdateSourceTrigger='PropertyChanged'}"/>
并在您的视图模型中定义 MyTextProperty
,如下所示:
public class GroupedTenantNameViewModel {
public string MyTextProperty { get; set; }
}
并使您的 FiltersState.GroupedTenantNames
collection 成为 collection 项中的 GroupedTenantNameViewModel
项。即使 ItemsControl
re-generates 所有项目,此 collection 仍将持久存在,并且绑定将负责将数据放回原处。
如果您根本不使用 MVVM 模式,那么我建议您稍微研究一下它,因为它旨在以正确的方式处理绑定!
我有一个在 DataTemplate 中使用的自定义用户控件。在这个用户控件中,我有一个文本框。以前,当我实现此用户控件时,我不会遇到任何问题,但现在我已将其放入 Datatemplate 中,似乎出了点问题。
每次我在文本框中输入内容时,代码运行正常,但在我的触发器结束时,用户控件构造函数被再次调用并清除我的文本框。 (隐藏的线程调用它,所以我什至不知道从哪里开始寻找这个 unintuitive 调用的来源)
我正在尝试找出导致此构造函数再次触发的原因。其他绑定似乎运行良好,并且正在填充和显示正确的信息。只是在所有内容解析并清除 ui 控件的内部变量后再次调用的构造函数。
当前执行:
我在文本框中输入。触发器获取我的文本框值相应地过滤列表,然后调用构造函数并将文本框重置为默认值“”。
期望执行:
我在文本框中输入。触发器获取我的文本框值相应地过滤列表。
<UserControl x:Class="Analytics_Module.Views.TenantProfileFilterFieldsView"
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"
xmlns:local="clr-namespace:Analytics_Module.Views"
xmlns:vm="clr-namespace:Analytics_Module.ViewModels"
xmlns:uiComponents="clr-namespace:Analytics_Module.UI_Components"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<DockPanel>
<DockPanel.DataContext>
<vm:TenantProfileFilterFieldsViewModel x:Name="test"/>
</DockPanel.DataContext>
.....
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding FiltersState.GroupedTenantNames, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<uiComponents:neoCombobox
LabelText="Tenant Names"
ListBoxItems="{Binding StaticLists.TenantNames, ElementName=test}"
DisplayListBoxItems ="{Binding}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
自定义用户控制
<UserControl x:Class="Analytics_Module.UI_Components.neoCombobox"
x:Name="parent"
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"
xmlns:model="clr-namespace:Analytics_Module.Models"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel DataContext="{Binding ElementName=parent}" Width="200">
<Label Name="ComboboxLabel"
Content="{Binding Path=LabelText, FallbackValue='Error'}" Margin="5"/>
<TextBox Name="InputField"
Text="{Binding Path=TextBoxValue, Mode=TwoWay, FallbackValue='Error', UpdateSourceTrigger='PropertyChanged'}"/>
<!--TODO rename -->
<ListBox Name="Something"
ItemsSource="{Binding Path=DisplayListBoxItems, FallbackValue={}, Mode=TwoWay}" >
<ListBox.ItemTemplate >
<DataTemplate >
<StackPanel>
<CheckBox Margin="-1"
Content="{Binding Name, FallbackValue='Error'}"
IsChecked="{Binding Check_Status, Mode=TwoWay, FallbackValue=true}">
</CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</UserControl>
用户控制后端
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Analytics_Module.Models;
using Analytics_Module.Utillity;
using System.Timers;
using System.Collections.Specialized;
namespace Analytics_Module.UI_Components
{
/// <summary>
/// Interaction logic for neoCombobox.xaml
/// </summary>
public partial class neoCombobox : UserControl
{
#region LabelText DP
public String LabelText
{
get { return (String)GetValue(LabelTextProperty); }
set { SetValue(LabelTextProperty, value); }
}
public static readonly DependencyProperty LabelTextProperty =
DependencyProperty.Register("LabelText",
typeof(string),
typeof(neoCombobox), new PropertyMetadata("")
);
#endregion
#region TextBoxValue DP
/// <summary>
/// Gets or sets the Value which is being displayed
/// </summary>
public String TextBoxValue
{
get { return (String)GetValue(TextBoxValueProperty); }
set { SetValue(TextBoxValueProperty, value); }
}
/// <summary>
/// Identified the TextBoxValue dependency property
/// </summary>
public static readonly DependencyProperty TextBoxValueProperty =
DependencyProperty.Register("TextBoxValue",
typeof(String),
typeof(neoCombobox),
new PropertyMetadata("")
);
#endregion
#region ListBoxItems DP
public ItemsChangeObservableCollection<MultiSelectDropDownListEntry> ListBoxItems
{
get { return (ItemsChangeObservableCollection<MultiSelectDropDownListEntry>)GetValue(ListBoxItemsProperty); }
set { SetValue(ListBoxItemsProperty, value); }
}
public static readonly DependencyProperty ListBoxItemsProperty =
DependencyProperty.Register("ListBoxItems",
typeof(ItemsChangeObservableCollection<MultiSelectDropDownListEntry>),
typeof(neoCombobox),
new PropertyMetadata(new ItemsChangeObservableCollection<MultiSelectDropDownListEntry>())
);
#endregion
#region DisplayListBoxItems DP
public ItemsChangeObservableCollection<MultiSelectDropDownListEntry> DisplayListBoxItems
{
get {
if (GetValue(DisplayListBoxItemsProperty) == null)
{
SetValue(DisplayListBoxItemsProperty, new ItemsChangeObservableCollection<MultiSelectDropDownListEntry>());
}
return (ItemsChangeObservableCollection<MultiSelectDropDownListEntry>)GetValue(DisplayListBoxItemsProperty);
}
set { SetValue(DisplayListBoxItemsProperty, value); }
}
public static readonly DependencyProperty DisplayListBoxItemsProperty =
DependencyProperty.Register("DisplayListBoxItems",
typeof(ItemsChangeObservableCollection<MultiSelectDropDownListEntry>),
typeof(neoCombobox),
new PropertyMetadata(new ItemsChangeObservableCollection<MultiSelectDropDownListEntry>())
);
#endregion
/// <summary>
/// _timer is used to determine if a user has stopped typing.
/// The timer is started when a user starts typing again or
/// types for the first time.
/// </summary>
private readonly Timer _timerKeyPress;
/// <summary>
/// _timer is used to determine if a user has left the typing.
/// The timer is started when a user starts typing again or
/// types for the first time.
/// </summary>
private readonly Timer _timerMouseLeave;
public neoCombobox()
{
if (TextBoxValue != "") return;
InitializeComponent();
_timerKeyPress = new Timer();
_timerKeyPress.Interval = 750;
_timerKeyPress.Elapsed += new ElapsedEventHandler(UserPausedTyping);
_timerKeyPress.AutoReset = false;
_timerMouseLeave = new Timer();
_timerMouseLeave.Interval = 550;
//_timerMouseLeave.Elapsed += new ElapsedEventHandler(UserLeft);
_timerMouseLeave.AutoReset = false;
}
//TODO Add property to determine if user preferes Mouse Leave of focus leave.
protected override void OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
Console.WriteLine("@@@@@@@@@@@@@@@OnPreviewGotKeyboardFocus");
_timerMouseLeave.Stop();
base.OnPreviewGotKeyboardFocus(e);
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
Console.WriteLine("------------OnPreviewLostKeyboardFocus");
_timerMouseLeave.Stop();
_timerMouseLeave.Start();
base.OnPreviewLostKeyboardFocus(e);
}
protected override void OnMouseEnter(MouseEventArgs e)
{
_timerMouseLeave.Stop();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(MouseEventArgs e)
{
_timerMouseLeave.Stop();
_timerMouseLeave.Start();
base.OnMouseLeave(e);
}
protected override void OnKeyUp(KeyEventArgs e)
{
_timerKeyPress.Stop();
_timerKeyPress.Start();
}
private void UserPausedTyping(object source, ElapsedEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
Console.WriteLine("@@@@@@@@@@@@@@@UserPausedTyping");
this.RefreshDisplayList();
});
}
private void UserLeft(object source, ElapsedEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
Console.WriteLine("@@@@@@@@@@@@@@@User Left");
this.TextBoxValue = "";
this.RefreshDisplayList();
});
}
protected void RefreshDisplayList()
{
int ItemsourceCount = 0;
foreach (MultiSelectDropDownListEntry entry in this.DisplayListBoxItems.ToList())
{
if (!entry.Check_Status) this.DisplayListBoxItems.Remove(entry);
}
if (this.TextBoxValue == "") return;
foreach (MultiSelectDropDownListEntry entry in this.ListBoxItems)
{
if (entry.Name.ToString().ToLower().Contains(this.TextBoxValue.ToLower()) && !this.DisplayListBoxItems.Contains(entry))
{
this.DisplayListBoxItems.Add(entry);
if (ItemsourceCount++ > 15) break;
}
}
}
}
}
您不能总是避免由 ItemsControl
重新创建容器,但是您可以通过将 TextBox
的 Text
属性 绑定到视图模型的属性,而不是自定义控件本身的属性。
而不是写作:
<TextBox Name="InputField" Text="{Binding Path=TextBoxValue, Mode=TwoWay, FallbackValue='Error', UpdateSourceTrigger='PropertyChanged'}"/>
也许写
<TextBox Name="InputField" Text="{Binding MyTextProperty, Mode=TwoWay, FallbackValue='Error', UpdateSourceTrigger='PropertyChanged'}"/>
并在您的视图模型中定义 MyTextProperty
,如下所示:
public class GroupedTenantNameViewModel {
public string MyTextProperty { get; set; }
}
并使您的 FiltersState.GroupedTenantNames
collection 成为 collection 项中的 GroupedTenantNameViewModel
项。即使 ItemsControl
re-generates 所有项目,此 collection 仍将持久存在,并且绑定将负责将数据放回原处。
如果您根本不使用 MVVM 模式,那么我建议您稍微研究一下它,因为它旨在以正确的方式处理绑定!