如何防止构造函数在值更改时被调用

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 重新创建容器,但是您可以通过将 TextBoxText 属性 绑定到视图模型的属性,而不是自定义控件本身的属性。

而不是写作:

<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 模式,那么我建议您稍微研究一下它,因为它旨在以正确的方式处理绑定!