我正在研究一个受WPF Auto Complete启发的名为select 2的自定义控件。问题是在OnApplyTemplate (为自定义控件初始化了所有控件)之前,调用了我的OnSelectionChanged (与一个名为SelectedItem的Dependecy属性绑定)。由于是在更新模式下,该项目未像OnApplyTemplate那样被选中
using Helpers.CustomControls.AutoComplete;
using Helpers.CustomControls.AutoComplete.CustomPopupControl;
using Helpers.CustomControls.AutoComplete.Interfaces;
using System;
using System.Collections;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace Helpers.CustomControls.AutoComplete
{
[TemplatePart(Name = PartGridContainer, Type = typeof(Grid))]
[TemplatePart(Name = PartEditor, Type = typeof(TextBox))]
[TemplatePart(Name = PartSelect2, Type = typeof(TextBox))]
[TemplatePart(Name = PartSuggestionBoxContainer, Type = typeof(StackPanel))]
[TemplatePart(Name = PartPopup, Type = typeof(Popup))]
[TemplatePart(Name = PartSelector, Type = typeof(Selector))]
[TemplatePart(Name = Select2DownArrow, Type = typeof(Path))]
[TemplatePart(Name = Select2UpArrow, Type = typeof(Path))]
public class AutoCompleteTextBox : Control
{
#region "Fields"
public const string PartGridContainer = "PART_GridContainer";
public const string PartEditor = "PART_Editor";
public const string PartPopup = "PART_Popup";
public const string PartSelect2 = "PART_Select2";
public const string PartSuggestionBoxContainer = "PART_SuggestionBoxContainer";
public const string PartSelector = "PART_Selector";
public const string Select2UpArrow = "Select2UpArrow";
public const string Select2DownArrow = "Select2DownArrow";
public static readonly DependencyProperty DelayProperty = DependencyProperty.Register("Delay", typeof(int), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(200));
public static readonly DependencyProperty DisplayMemberProperty = DependencyProperty.Register("DisplayMember", typeof(string), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(string.Empty));
public static readonly DependencyProperty IconPlacementProperty = DependencyProperty.Register("IconPlacement", typeof(IconPlacement), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(IconPlacement.Left));
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(object), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty IconVisibilityProperty = DependencyProperty.Register("IconVisibility", typeof(Visibility), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(Visibility.Visible));
public static readonly DependencyProperty IsSelect2Property = DependencyProperty.Register("IsSelect2", typeof(bool), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty IsDropDownOpenProperty = DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register("IsLoading", typeof(bool), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty ItemTemplateSelectorProperty = DependencyProperty.Register("ItemTemplateSelector", typeof(DataTemplateSelector), typeof(AutoCompleteTextBox));
public static readonly DependencyProperty LoadingContentProperty = DependencyProperty.Register("LoadingContent", typeof(object), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty ProviderProperty = DependencyProperty.Register("Provider", typeof(ISuggestionProvider), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null, OnSelectedItemChanged));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(string.Empty));
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register("MaxLength", typeof(int), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(0));
public static readonly DependencyProperty CharacterCasingProperty = DependencyProperty.Register("CharacterCasing", typeof(CharacterCasing), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(CharacterCasing.Normal));
public static readonly DependencyProperty MaxPopUpHeightProperty = DependencyProperty.Register("MaxPopUpHeight", typeof(int), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(600));
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register("Watermark", typeof(string), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(string.Empty));
public static readonly DependencyProperty SuggestionBackgroundProperty = DependencyProperty.Register("SuggestionBackground", typeof(Brush), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(Brushes.White));
private bool _isUpdatingText;
private bool _selectionCancelled;
private SuggestionsAdapter _suggestionsAdapter;
#endregion
#region "Constructors"
static AutoCompleteTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(typeof(AutoCompleteTextBox)));
}
#endregion
#region "Properties"
public int MaxPopupHeight
{
get => (int)GetValue(MaxPopUpHeightProperty);
set => SetValue(MaxPopUpHeightProperty, value);
}
public BindingEvaluator BindingEvaluator { get; set; }
public CharacterCasing CharacterCasing
{
get => (CharacterCasing)GetValue(CharacterCasingProperty);
set => SetValue(CharacterCasingProperty, value);
}
public int MaxLength
{
get => (int)GetValue(MaxLengthProperty);
set => SetValue(MaxLengthProperty, value);
}
public int Delay
{
get => (int)GetValue(DelayProperty);
set => SetValue(DelayProperty, value);
}
public string DisplayMember
{
get => (string)GetValue(DisplayMemberProperty);
set => SetValue(DisplayMemberProperty, value);
}
private TextBox Select2 { get; set; }
private TextBox Editor { get; set; }
public DispatcherTimer FetchTimer { get; set; }
public string Filter { get; set; }
public object Icon
{
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
public IconPlacement IconPlacement
{
get => (IconPlacement)GetValue(IconPlacementProperty);
set => SetValue(IconPlacementProperty, value);
}
public Visibility IconVisibility
{
get => (Visibility)GetValue(IconVisibilityProperty);
set => SetValue(IconVisibilityProperty, value);
}
public bool IsSelect2
{
get => (bool)GetValue(IsSelect2Property);
set => SetValue(IsSelect2Property, value);
}
public bool IsDropDownOpen
{
get => (bool)GetValue(IsDropDownOpenProperty);
set => SetValue(IsDropDownOpenProperty, value);
}
public bool IsLoading
{
get => (bool)GetValue(IsLoadingProperty);
set => SetValue(IsLoadingProperty, value);
}
public bool IsReadOnly
{
get => (bool)GetValue(IsReadOnlyProperty);
set => SetValue(IsReadOnlyProperty, value);
}
public Selector ItemsSelector { get; set; }
public DataTemplate ItemTemplate
{
get => (DataTemplate)GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
public DataTemplateSelector ItemTemplateSelector
{
get => ((DataTemplateSelector)(GetValue(ItemTemplateSelectorProperty)));
set => SetValue(ItemTemplateSelectorProperty, value);
}
public object LoadingContent
{
get => GetValue(LoadingContentProperty);
set => SetValue(LoadingContentProperty, value);
}
private Grid GridContainer { get; set; }
private Popup Select2Popup { get; set; }
private StackPanel SuggestionBoxContainer { get; set; }
private Popup Popup { get; set; }
public ISuggestionProvider Provider
{
get => (ISuggestionProvider)GetValue(ProviderProperty);
set => SetValue(ProviderProperty, value);
}
public object SelectedItem
{
get => GetValue(SelectedItemProperty);
set => SetValue(SelectedItemProperty, value);
}
public SelectionAdapter SelectionAdapter { get; set; }
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public string Watermark
{
get => (string)GetValue(WatermarkProperty);
set => SetValue(WatermarkProperty, value);
}
public Brush SuggestionBackground
{
get => (Brush)GetValue(SuggestionBackgroundProperty);
set => SetValue(SuggestionBackgroundProperty, value);
}
#endregion
#region "Methods"
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
GridContainer = Template.FindName(PartGridContainer, this) as Grid;
Select2 = Template.FindName(PartSelect2, this) as TextBox;
Editor = Template.FindName(PartEditor, this) as TextBox;
Popup = Template.FindName(PartPopup, this) as Popup;
ItemsSelector = Template.FindName(PartSelector, this) as Selector;
if (IsSelect2)
{
SuggestionBoxContainer = Template.FindName(PartSuggestionBoxContainer, this) as StackPanel;
if (GridContainer != null)
{
GridContainer.Children.Remove(SuggestionBoxContainer);
Select2Popup = InitializeSelect2Popup();
Select2Popup.Child = SuggestionBoxContainer;
GridContainer.Children.Add(Select2Popup);
Grid.SetRow(Select2Popup, 1);
}
if (Select2 != null)
{
Select2.PreviewKeyDown += OnSelect2PreviewKeyDown;
Select2.PreviewMouseDown += OnSelect2PreviewMouseDown;
}
}
else
{
if (Select2 != null)
{
Select2.Visibility = Visibility.Collapsed;
}
}
BindingEvaluator = new BindingEvaluator(new Binding(DisplayMember));
if (Editor != null)
{
Editor.TextChanged += OnEditorTextChanged;
Editor.PreviewKeyDown += OnEditorKeyDown;
Editor.LostFocus += OnEditorLostFocus;
if (SelectedItem != null)
{
_isUpdatingText = true;
Editor.Text = BindingEvaluator.Evaluate(SelectedItem);
_isUpdatingText = false;
}
}
if (Popup != null)
{
Popup.StaysOpen = false;
Popup.Opened += OnPopupOpened;
Popup.Closed += OnPopupClosed;
}
if (ItemsSelector != null)
{
SelectionAdapter = new SelectionAdapter(ItemsSelector);
SelectionAdapter.Commit += OnSelectionAdapterCommit;
SelectionAdapter.Cancel += OnSelectionAdapterCancel;
SelectionAdapter.SelectionChanged += OnSelectionAdapterSelectionChanged;
ItemsSelector.PreviewMouseDown += ItemsSelector_PreviewMouseDown;
}
}
public static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
AutoCompleteTextBox act = null;
act = d as AutoCompleteTextBox;
if (act != null)
{
if (act.Editor != null & !act._isUpdatingText)
{
act._isUpdatingText = true;
act.Editor.Text = act.BindingEvaluator.Evaluate(e.NewValue);
if (act.Select2 != null)
{
act.Select2.Text = act.BindingEvaluator.Evaluate(e.NewValue);
}
act._isUpdatingText = false;
}
}
}
private void ScrollToSelectedItem()
{
if (ItemsSelector is ListBox listBox && listBox.SelectedItem != null)
listBox.ScrollIntoView(listBox.SelectedItem);
}
private void ItemsSelector_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if ((e.OriginalSource as FrameworkElement)?.DataContext == null)
return;
if (!ItemsSelector.Items.Contains(((FrameworkElement)e.OriginalSource)?.DataContext))
return;
ItemsSelector.SelectedItem = ((FrameworkElement)e.OriginalSource)?.DataContext;
OnSelectionAdapterCommit();
}
private string GetDisplayText(object dataItem)
{
if (BindingEvaluator == null)
{
BindingEvaluator = new BindingEvaluator(new Binding(DisplayMember));
}
if (dataItem == null)
{
return string.Empty;
}
if (string.IsNullOrEmpty(DisplayMember))
{
return dataItem.ToString();
}
return BindingEvaluator.Evaluate(dataItem);
}
private void OnEditorKeyDown(object sender, KeyEventArgs e)
{
if (IsSelect2 && (e.Key == Key.Escape || (IsDropDownOpen == false && e.Key == Key.Enter)))
{
CloseSelect2Suggestions();
e.Handled = true;
return;
}
if (SelectionAdapter != null)
{
if (IsDropDownOpen)
SelectionAdapter.HandleKeyDown(e);
else
IsDropDownOpen = e.Key == Key.Down || e.Key == Key.Up;
}
}
private void OnEditorLostFocus(object sender, RoutedEventArgs e)
{
if (!IsKeyboardFocusWithin)
{
IsDropDownOpen = false;
}
}
private void OnEditorTextChanged(object sender, TextChangedEventArgs e)
{
if (_isUpdatingText)
return;
if (FetchTimer == null)
{
FetchTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(Delay) };
FetchTimer.Tick += OnFetchTimerTick;
}
FetchTimer.IsEnabled = false;
FetchTimer.Stop();
if (IsSelect2 == false)
{
SetSelectedItem(null);
}
if (Editor.Text.Length > 0)
{
FetchTimer.IsEnabled = true;
FetchTimer.Start();
}
else
{
IsDropDownOpen = false;
}
}
private void OnSelect2PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Enter && e.Key != Key.Tab)
{
e.Handled = true;
return;
}
if (e.Key == Key.Enter)
{
ToggleSelect2();
}
}
private void OnSelect2PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
ToggleSelect2();
e.Handled = true;
return;
}
private void OnFetchTimerTick(object sender, EventArgs e)
{
FetchTimer.IsEnabled = false;
FetchTimer.Stop();
if (Provider != null && ItemsSelector != null)
{
Filter = Editor.Text;
if (_suggestionsAdapter == null)
{
_suggestionsAdapter = new SuggestionsAdapter(this);
}
_suggestionsAdapter.GetSuggestions(Filter);
}
}
private void OnPopupClosed(object sender, EventArgs e)
{
if (!_selectionCancelled)
{
OnSelectionAdapterCommit();
}
}
private void OnPopupOpened(object sender, EventArgs e)
{
_selectionCancelled = false;
ItemsSelector.SelectedItem = SelectedItem;
}
private void OnSelectionAdapterCancel()
{
_isUpdatingText = true;
Editor.Text = SelectedItem == null ? Filter : GetDisplayText(SelectedItem);
Editor.SelectionStart = Editor.Text.Length;
Editor.SelectionLength = 0;
_isUpdatingText = false;
IsDropDownOpen = false;
_selectionCancelled = true;
}
private void OnSelectionAdapterCommit()
{
if (ItemsSelector.SelectedItem != null)
{
SelectedItem = ItemsSelector.SelectedItem;
_isUpdatingText = true;
Editor.Text = GetDisplayText(ItemsSelector.SelectedItem);
if (IsSelect2 && Select2 != null)
Select2.Text = GetDisplayText(ItemsSelector.SelectedItem);
SetSelectedItem(ItemsSelector.SelectedItem);
_isUpdatingText = false;
IsDropDownOpen = false;
if (IsSelect2)
CloseSelect2Suggestions();
}
}
private void OnSelectionAdapterSelectionChanged()
{
_isUpdatingText = true;
Editor.Text = ItemsSelector.SelectedItem == null ? Filter : GetDisplayText(ItemsSelector.SelectedItem);
Editor.SelectionStart = Editor.Text.Length;
Editor.SelectionLength = 0;
ScrollToSelectedItem();
_isUpdatingText = false;
}
private void SetSelectedItem(object item)
{
_isUpdatingText = true;
SelectedItem = item;
_isUpdatingText = false;
}
#endregion
#region[Supportive Functions]
private void OpenSelect2Suggestions()
{
if (Select2Popup.IsOpen == false)
{
if (Select2Popup != null)
Select2Popup.IsOpen = true;
Editor?.Focus();
IsDropDownOpen = false;
var upArrow = (Template.FindName(Select2UpArrow, this) as Path);
var downArrow = (Template.FindName(Select2DownArrow, this) as Path);
if (upArrow != null && downArrow != null)
{
upArrow.Visibility = Visibility.Visible;
downArrow.Visibility = Visibility.Collapsed;
}
}
}
private void CloseSelect2Suggestions()
{
if (Select2Popup.IsOpen == true)
{
if (Select2Popup != null)
Select2Popup.IsOpen = false;
Select2?.Focus();
IsDropDownOpen = false;
}
}
private void ToggleSelect2()
{
if (Select2Popup.IsOpen)
CloseSelect2Suggestions();
else
OpenSelect2Suggestions();
}
private PopupNonTopmost InitializeSelect2Popup()
{
PopupNonTopmost select2Popup = new PopupNonTopmost();
select2Popup.HorizontalAlignment = HorizontalAlignment.Stretch;
select2Popup.Width = 300;
select2Popup.IsOpen = false;
select2Popup.PlacementTarget = Select2;
select2Popup.Placement = PlacementMode.Bottom;
select2Popup.AllowsTransparency = true;
select2Popup.PopupAnimation = PopupAnimation.Slide;
select2Popup.Focusable = false;
Binding widthBinding = new Binding("ActualWidth") { ElementName = PartSelect2 };
select2Popup.SetBinding(FrameworkElement.WidthProperty, widthBinding);
return select2Popup;
}
#endregion
#region "Nested Types"
private class SuggestionsAdapter
{
#region "Fields"
private readonly AutoCompleteTextBox _actb;
private string _filter;
#endregion
#region "Constructors"
public SuggestionsAdapter(AutoCompleteTextBox actb)
{
_actb = actb;
}
#endregion
#region "Methods"
public void GetSuggestions(string searchText)
{
_filter = searchText;
_actb.IsLoading = true;
_actb.IsDropDownOpen = true;
_actb.ItemsSelector.ItemsSource = null;
ParameterizedThreadStart thInfo = GetSuggestionsAsync;
Thread th = new Thread(thInfo);
th.Start(new object[] {
searchText,
_actb.Provider
});
}
private void DisplaySuggestions(IEnumerable suggestions, string filter)
{
if (_filter != filter)
{
return;
}
if (_actb.IsDropDownOpen)
{
_actb.IsLoading = false;
_actb.ItemsSelector.ItemsSource = suggestions;
var interfaceType = typeof(IAutoCompleteItem);
_actb.ItemsSelector.DisplayMemberPath = interfaceType.GetProperties().FirstOrDefault().Name;
_actb.IsDropDownOpen = _actb.ItemsSelector.HasItems;
}
}
private void GetSuggestionsAsync(object param)
{
if (param is object[] args)
{
string searchText = Convert.ToString(args[0]);
if (args[1] is ISuggestionProvider provider)
{
IEnumerable list = provider.GetSuggestions(searchText);
_actb.Dispatcher.BeginInvoke(new Action<IEnumerable, string>(DisplaySuggestions), DispatcherPriority.Background, list, searchText);
}
}
}
#endregion
}
#endregion
}
}
这就是我在XAML中调用它的方式
<ac:AutoCompleteTextBox x:Name="searchFloors" Tag="Floor" IsSelect2="True" Style="{StaticResource Select2Style}" DisplayMember="SearchText" SelectedItem="{Binding Floor,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
这是我的模特资源
public FloorViewModel FloorModel
{
get { return floorModel; }
set
{
floorModel = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("FloorModel");
}
}
首次更新
这仅在两种情况下发生
1)在运行时创建的窗口。
Window window = new Window();
window.Content = UpdateShelf(MainWindow, Shelf, GridList.ResultList)
2)使用ContentControl时。
Xaml:
<ContentControl x:Name="updateControl"/>
背后的代码
var updateUC = new UpdateShelf(MainWindow, Shelf, GridList.ResultList);
ShowUpdateUC();
updateControl.Content = updateUC;
updateUC.GoBack += UpdateGoBack_Event;
更新架是用户控件。为了简单起见,我对UpdateShelf构造函数进行了如下编码:
public UpdateShelf(MainDashboard MainWindow, ShelfViewModel shelf, ObservableCollection<ShelfViewModel> gridList)
{
InitializeComponent();
shelfViewModel = shelf ?? new ShelfViewModel();
shelfViewModel.FloorModel = new FloorViewModel(){Name="Floor One"};
DataContext = shelfViewModel;
}
第二次更新
如果我从构造函数中删除Datacontext到Loaded Function。它可以按预期工作,但是为什么会发生这种情况? 在加载的函数中设置datacontext更好吗?
public UpdateShelf(MainDashboard MainWindow, ShelfViewModel shelf, ObservableCollection<ShelfViewModel> gridList)
{
InitializeComponent();
shelfViewModel = shelf ?? new ShelfViewModel();
shelfViewModel.FloorModel = new FloorViewModel(){Name="Floor One"};
#region[Events Registrations]
Loaded += new RoutedEventHandler(UpdateShelfView_Loaded);
Unloaded += new RoutedEventHandler(UpdateShelfView_UnLoaded);
#endregion
}
void UpdateShelfView_Loaded(object sender, RoutedEventArgs e)
{
DataContext = shelfViewModel;
}
答案 0 :(得分:1)
创建一个Initialize()
方法,该方法执行控件按预期执行所需的初始化操作(例如,从此处调用回调)。从OnApplyTemplate
覆盖或FrameworkElement.Loaded
事件处理程序中调用此方法:
public class AutoCompleteTextBox : Control
{
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
"SelectedItem",
typeof(object),
typeof(AutoCompleteTextBox),
new FrameworkPropertyMetadata(null, OnSelectedItemChanged));
public object SelectedItem { get => (object)GetValue(AutoCompleteTextBox.SelectedItemProperty); set => SetValue(AutoCompleteTextBox.SelectedItemProperty, value); }
private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
...
}
private void Initialize()
{
AutoCompleteTextBox.OnSelectedItemChanged(this, new DependencyPropertyChangedEventArgs(AutoCompleteTextBox.SelectedItemProperty, this.SelectedItem, this.SelectedItem));
}
public AutoCompleteTextBox()
{
InitializeComponent();
this.Loaded += () => Initialize();
}
}