顯示具有 MVVM 標籤的文章。 顯示所有文章
顯示具有 MVVM 標籤的文章。 顯示所有文章
AutoCompleteTextBox: First Meet

AutoCompleteTextBox: First Meet

AutoCompleteTextBox in DotNetProjects.Wpf.Toolkit

DotNetProjects.Wpf.Toolkit 套件提供了AutoCompleteTextBox,它是一個可以在 WPF 程式裡面使用的控制項。除了像TextBox一樣,提供使用者輸入文字外,在使用者輸入文字的同時,能根據使用者的輸入及時提供符合自訂條件的建議字串清單,加快使用者的輸入速度。

要使用AutoCompleteTextBox,使用者必須先提供候選的清單以及過濾的方法。

實作過程

  • 新開始名為TestAutoCompleteTextBox的WPF Application方案
  • 在TestAutoCompleteTextBox專案中用Nuget Manager 加入DotNetProjects.Wpf.Toolkit
  • 在TestAutoCompleteTextBox專案中加入類別檔案MainWindowViewModel.cs用來當作MainWindow.xaml的視模(ViewModel)
  • 在MainWindowViewModel類別實作
    • INotifyPropertyChanged介面
    • 可當作繫結來源屬性的Name,類型為string
    • 可當作繫結來源屬性的Names屬性,類型為List<string> ,用Names來當作候選清單
    • 可當作繫結來源的Filter屬性,Filter是個過濾器,利用使用者的輸入內容,從候選清單Names中挑出子清單供使用者挑選。
  • 在 MainWindow.xaml 中:
    • Window的屬性增加 xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=DotNetProjects.Input.Toolkit"
    • Window的Resources裡面增加資源<local:MainWindowViewModel x:Key="viewmodel" />
    • 設定Window裡的DataContex為資源裡的viewmodel
    • Window裡增加AutoCompleteTextBox的案例。將其屬性Text以雙向繫結的方式與視模端的來源屬性Name。將其屬性ItemFilter與視模端的來源屬性Filter繫結。將其屬性ItemSource與視模端的來源屬性Names繫結。將MinimumPrefixLength屬性設定為"1",代表使用者只要打1個字元,AutoCompleteTextBox就開始提供合格清單。

程式碼

MainWindowViewModel.cs

using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Controls;
namespace TestAutoCompleteTextBox
{
    public class MainWindowViewModel:INotifyPropertyChanged
    {
        public MainWindowViewModel()
        {
            Names = new List<string>
            {
                "WPF","WCF","DotNet","Data Entity Framework"
            };
        }
        public List<string> Names { get; private set; }
        public event PropertyChangedEventHandler PropertyChanged;
        private string _Name;
        public string Name {
            get
            {
                return _Name;
            }
            set
            {
                if (value != _Name)
                {
                    _Name = value;
                    OnPropertyChanged("Name");
                }
            }
        }
        private void OnPropertyChanged(string PropertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
        }
        public AutoCompleteFilterPredicate<object> Filter
        {
            get
            {
                return (searchText, obj) => filter(searchText, obj);
            }
        }
        public bool filter(string editString, object obj)
        {
            string s = obj as string;
            return s.ToLower().Contains(editString.ToLower());
        }
    }
}

MainWindow.xaml

<Window x:Class="TestAutoCompleteTextBox.MainWindow"
  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:local="clr-namespace:TestAutoCompleteTextBox"
  xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=DotNetProjects.Input.Toolkit"  
  mc:Ignorable="d"
  Title="MainWindow" Height="350" Width="525">
  <Window.Resources>
    <local:MainWindowViewModel x:Key="viewmodel" />
  </Window.Resources>
  <StackPanel DataContext="{StaticResource viewmodel}">
    <TextBlock Text="{Binding Name}" />
    <StackPanel Orientation="Horizontal">
      <TextBlock Text="Input:" />
      <Controls:AutoCompleteBox Width="200"
        Text="{Binding Name, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
        ItemFilter="{Binding Filter}"
        ItemsSource="{Binding Names}"
        MinimumPrefixLength="1"   />
    </StackPanel>
  </StackPanel>
</Window>

註:假如細節的清單由物件所構成,則AutoCompleteTextBox可利用ItemTemplate屬性與ValueMemberPath屬性來完成任務。其中,ItemTemplate用來表現清單裡的物件,ValueMemberPath用來將所選物件之ValueMemberPath對應屬性值複製到AutoCompleteTextBox的Text屬性。

InteractionRequest

InteractionRequest

Interaction Request

View 端繫結 ViewModel 端所公布Interaction Request object。 當ViewModel端有需要時,用該Interaction Request object的 Raise函數發出請求,由View端加以處理。

NotificationRequest

ViewModel

public class MyViewModel : BindableBase
{
  public InteractionRequest<INotification> NotificationRequest { get; private set; }
 
  public MyViewModel(){
    ...
    this.NotificationRequest = new InteractionRequest<INotification>();
    ...
  }
  private void DoSomething(){
    ...
    this.NotificationRequest.Raise(
      new Notification { Content = "Notification Message", Title = "Notification" },
        n => { });
    ...
  }
}

View

<UserControl
...
  xmlns:prism="http://prismlibrary.com/"
  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
  ...
>
<i:Interaction.Triggers>
  <prism:InteractionRequestTrigger SourceObject="{Binding NotificationRequest, Mode=OneWay}">
    <!-- This PopupWindowAction does not have a custom view defined, therefore it will try to use a DefaultConfirmationWindow -->
    <!-- which is a window used by default by Prism to shown IConfirmation implementations -->
    <!-- That window will be show as a modal dialog and centered over this window -->
    <prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True"/>
  </prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
...
</UserControl>

ConfirmationRequest

ViewModel

public class MyViewModel : BindableBase
{
  public InteractionRequest<IConfirmation> ConfirmationRequest { get; private set; }
  public MyViewModel(){
    ...
    this.ConfirmationRequest = new InteractionRequest<IConfirmation>();
    ...
  }
  private void DoSomething(){
    ...
    bool isConfirmed = false;
    this.ConfirmationRequest.Raise(
      new Confirmation {
        Content = "Do you really want to delete PItem " + CurPItem.Name + "?",
        Title = "Confirmation" },
      c => { isConfirmed = c.Confirmed; });
    if (isConfirmed)
    ...
  }
}

View

<UserControl
...
  xmlns:prism="http://prismlibrary.com/"
  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
  ...
>
<i:Interaction.Triggers>
  <prism:InteractionRequestTrigger SourceObject="{Binding ConfirmationRequest, Mode=OneWay}">
    <!-- This PopupWindowAction does not have a custom view defined, therefore it will try to use a DefaultNotificationWindow -->
    <!-- which is a window used by default by Prism to shown INotification implementations -->
    <!-- That window will be show as a modal dialog and centered over this window -->
    <prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True"/>
  </prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
...
</UserControl>
SelectionChangedSelectedItemsTutorial

SelectionChangedSelectedItemsTutorial

InvokeCommandAction

介紹 InvokeCommandAction

Prism 的 InvokeCommandAction 比 Blend 的 InvokeCommandAction 功能強大。

Advanced MVVM Scenarios Using the Prism Library for WPF 提到:

Prism’s InvokeCommandAction differs from the class of the same name in the Blend SDK in two ways.

First, the Prism InvokeCommandAction updates the enabled state of the associated control based on the return value of the command’s CanExecute method.

Second, the Prism InvokeCommandAction uses the EventArgs parameter passed to it from the parent trigger, passing it to the associated command if the CommandParameter is not set.

Prism’s InvokeCommandAction has a property called TriggerParameterPath that is used to specify the member property of the EventArgs passed as the command parameter.

練習

View的部分

<UserControl x:Class="ModuleSelectionChange.Views.ViewA"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:prism="http://prismlibrary.com/"             
 prism:ViewModelLocator.AutoWireViewModel="True"
 xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
 >
<Grid>
...
<ListBox ItemsSource="{Binding Names}" SelectionMode="Extended" >
 <i:Interaction.Triggers>
  <!-- This event trigger will execute the action when the corresponding event is raised by the ListBox. -->
  <i:EventTrigger EventName="SelectionChanged">
      <!-- This action will invoke the selected command in the view model and pass the parameters of the event to it. -->
      <prism:InvokeCommandAction Command="{Binding AddedCommand}" TriggerParameterPath="AddedItems" />
  </i:EventTrigger>
  <i:EventTrigger EventName="SelectionChanged">
      <prism:InvokeCommandAction Command="{Binding RemovedCommand}" TriggerParameterPath="RemovedItems" />
  </i:EventTrigger>
  <i:EventTrigger EventName="SelectionChanged">
      <prism:InvokeCommandAction Command="{Binding SelectedItemsCommand}" TriggerParameterPath="Source.SelectedItems" />
  </i:EventTrigger>
  <i:EventTrigger EventName="SelectionChanged">
      <prism:InvokeCommandAction Command="{Binding SelectedCommand}" TriggerParameterPath="Source" />
  </i:EventTrigger>
  <i:EventTrigger EventName="SelectionChanged">
      <prism:InvokeCommandAction Command="{Binding EventArgsCommand}"  />
  </i:EventTrigger>
 </i:Interaction.Triggers>
</ListBox>
...
</Grid>

ViewModel 的部分

public class ViewAViewModel : BindableBase
{
    public ObservableCollection<string> Names { get; private set; }
    private string _Added;
    public string Added
    {
        get { return _Added; }
        set { SetProperty(ref _Added, value); }
    }
    private string _Removed;
    public string Removed
    {
        get { return _Removed; }
        set { SetProperty(ref _Removed, value); }
    }
    private string _Selected;
    public string Selected
    {
        get { return _Selected; }
        set { SetProperty(ref _Selected, value); }
    }
    private string _SelectedItems;
    public string SelectedItems
    {
        get { return _SelectedItems; }
        set { SetProperty(ref _SelectedItems, value); }
    }
    private string _EventArgs;
    public string EventArgs
    {
        get { return _EventArgs; }
        set { SetProperty(ref _EventArgs, value); }
    }
    public DelegateCommand<object> SelectedCommand { get; private set; }
    public DelegateCommand<object[]> AddedCommand { get; private set; }
    public DelegateCommand<object[]> RemovedCommand { get; private set; }
    public DelegateCommand<System.Collections.ICollection> SelectedItemsCommand { get; private set; }
    public DelegateCommand<System.Windows.Controls.SelectionChangedEventArgs> EventArgsCommand { get; private set; } 
    private void GetAddedItem(object[] listOfItems)
    {
        string res = GetList(listOfItems);
        Added = res;
    }
    private void GetRemovedItem(object[] listOfItems)
    {
        string res = GetList(listOfItems);
        Removed = res;
    }
    private void GetSelectedItems2(System.Collections.ICollection listOfItems)
    {
        string res = "";
        foreach(var x in listOfItems)
            res += x + "\n";
        SelectedItems = res;
    }
    private void GetEventArgs(System.Windows.Controls.SelectionChangedEventArgs args)
    {
        string res = "Added:\n";
        for (int i = 0; i < args.AddedItems.Count; i++)
            res += args.AddedItems[i]+"\n";
        res += "Removed:\r\n";
        for (int i = 0; i < args.RemovedItems.Count; i++)
            res += args.RemovedItems[i]+"\n";
        res += "Selected:\n";
        var items = (args.Source as System.Windows.Controls.ListBox).SelectedItems;
        for (int i = 0; i < items.Count; i++)
            res += items[i] + "\n";
        EventArgs = res;
    }
    private static string GetList(object[] listOfItems)
    {
        string res = "";
        if (listOfItems != null && listOfItems.Count() > 0)
        {
            for (int i = 0; i < listOfItems.Count(); i++)
                res = res + listOfItems[i].ToString() + "\r\n";
        }
        return res;
    }
    private void GetSelectedItems(object source)
    {
        var listOfItems = (source as System.Windows.Controls.ListBox).SelectedItems;
        string res = "";
        if (listOfItems != null && listOfItems.Count > 0)
        {
            for (int i = 0; i < listOfItems.Count; i++)
                res = res + listOfItems[i].ToString() + "\r\n";
        }
        Selected = res;
    }
    public ViewAViewModel()
    {
        Names = new ObservableCollection<string>() {"Yang","Hon","Jang"};
        this.AddedCommand = new DelegateCommand<object[]>(GetAddedItem);
        this.RemovedCommand = new DelegateCommand<object[]>(GetRemovedItem);
        this.SelectedCommand = new DelegateCommand<object>(GetSelectedItems);
        this.SelectedItemsCommand = new DelegateCommand<System.Collections.ICollection>(GetSelectedItems2);
        this.EventArgsCommand = new DelegateCommand<System.Windows.Controls.SelectionChangedEventArgs>(GetEventArgs);
    }
}

說明

ListBox 發生SelectionChanged事件的時候,EventArgs裡的屬性有AddedItems,RemovedItems,Source。其中,Source是ListBox本身。

下面我們利用各種屬性透過 TriggerParameterPath 傳遞給Command當參數。

AddedItems

<prism:InvokeCommandAction Command="{Binding AddedCommand}" TriggerParameterPath="AddedItems" />

AddedCommand的格式為

public DelegateCommand<object[]> AddedCommand { get; private set; }

RemovedItems

<prism:InvokeCommandAction Command="{Binding RemovedCommand}" TriggerParameterPath="RemovedItems" />

RemovedCommand的格式為

public DelegateCommand<object[]> RemovedCommand { get; private set; }

Source

<prism:InvokeCommandAction Command="{Binding SelectedCommand}" TriggerParameterPath="Source" />

SelectedCommand的格式為

public DelegateCommand<object> SelectedCommand { get; private set; }

Source.SelectedItems

<prism:InvokeCommandAction Command="{Binding SelectedItemsCommand}" TriggerParameterPath="Source.SelectedItems" />

SelectedItemsCommand的格式為

public DelegateCommand<System.Collections.ICollection> SelectedItemsCommand { get; private set; }

EventArgs

<prism:InvokeCommandAction Command="{Binding EventArgsCommand}"  />

EventArgsCommand 的格式為

public DelegateCommand<System.Windows.Controls.SelectionChangedEventArgs> EventArgsCommand { get; private set; } 
技術提供:Blogger.