WPF ComboBox – ItemTemplate vs. IsEditable

huangapple go评论65阅读模式
英文:

WPF ComboBox - ItemTemplate vs. IsEditable

问题

I created a combo box with an item template. The template includes a status icon and text:

我创建了一个包含项目模板的组合框。模板包括一个状态图标和文本:

Here is the XAML:

以下是XAML代码:

<DataTemplate x:Key="ItemTemplate">
    <WrapPanel>
        <Image Width="24" Height="24" Stretch="Fill" Source="{Binding StateImage}" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0,0,15,0"/>
        <Label Content="{Binding Address}" VerticalAlignment="Center" HorizontalAlignment="Center" />
    </WrapPanel>
</DataTemplate>

<ComboBox x:Name="CbxClients" HorizontalAlignment="Center" VerticalAlignment="Top" ItemTemplate="{StaticResource ItemTemplate}" Width="320" Height="24" IsEditable="False" />

Now I want to make the combo box editable, so that the user can enter new strings, which should then be added to the list. Thus I set "IsEditable" to true. This is the result:

现在我想使组合框可编辑,以便用户可以输入新的字符串,然后将其添加到列表中。因此,我将“IsEditable”设置为true。这是结果:

WPF ComboBox – ItemTemplate vs. IsEditable

This change causes a problem: the icon is now no longer shown in the combo box (only in the drop-down area). While editing/entering a string, this would be fine, but after the input has been committed, I would expect the new item to be added to the list and an icon to be shown.

这个更改引发了一个问题:图标现在不再显示在组合框中(只显示在下拉区域)。在编辑/输入字符串时,这是可以接受的,但在输入被提交后,我希望新项目被添加到列表中,并显示一个图标。

Is there any way I can achieve this behavior?

是否有任何方法可以实现这种行为?

英文:

I created a combo box with an item template. The template includes a status icon and text:

WPF ComboBox – ItemTemplate vs. IsEditable

Here is the XAML:

    &lt;DataTemplate x:Key=&quot;ItemTemplate&quot;&gt;
      &lt;WrapPanel&gt;
        &lt;Image Width=&quot;24&quot; Height=&quot;24&quot; Stretch=&quot;Fill&quot; Source=&quot;{Binding StateImage}&quot; VerticalAlignment=&quot;Center&quot; HorizontalAlignment=&quot;Center&quot; Margin=&quot;0,0,15,0&quot;/&gt;
        &lt;Label Content=&quot;{Binding Address}&quot; VerticalAlignment=&quot;Center&quot; HorizontalAlignment=&quot;Center&quot; /&gt;
      &lt;/WrapPanel&gt;
    &lt;/DataTemplate&gt;

&lt;ComboBox x:Name=&quot;CbxClients&quot; HorizontalAlignment=&quot;Center&quot; VerticalAlignment=&quot;Top&quot; ItemTemplate=&quot;{StaticResource ItemTemplate}&quot; Width=&quot;320&quot; Height=&quot;24&quot; IsEditable=&quot;False&quot; /&gt;

Now I want to make the the combo box editable, so that the user can enter new strings, which hould then be added to the list. Thus I set "IsEditable" to true. This is the result:

WPF ComboBox – ItemTemplate vs. IsEditable

This change causes a problem: the icon is now no longer shown in the combo box (only in the drop down area). While editing/entering a string this would be fine, but after the input has been committed, I would expect the new item being added to the list and an icon to be shown.

Is there any way I can achieve this behavior?

答案1

得分: 2

以下是翻译好的内容:

  1. 问题: ComboBox 不包含简单的 string 项目。相反,您已经定义了一个 DataTemplate 来呈现更复杂的项目模型。
    解决方案: 关键问题在于 ComboBox 没有机会知道如何使用输入值构造项目模型的新实例。您必须明确执行这个操作。

  2. 问题: 由于您的 ComboBox 配置为处于编辑模式,TextBox 将只包含项目模型的文本表示(ComboBox 将在模型上调用 object.ToString 以获取文本表示)。
    解决方案: 没有现成的解决方案。如果 ComboBox.IsEditable 返回 falseComboBox 将使用 ContentPresenter 显示 ComboBox.SelectedItem。如果 ComboBox.IsEditable 返回 trueComboBox 将用 TextBox 替换 ContentPresenter 以允许编辑。现在,您可以覆盖原始的 ControlTemplate,在两个内容位置之间切换。例如,在 GotFocus 时显示 TextBox,在 LostFocus 时切换回 ContentPresenter
    您可以使用 XAML 设计器提取 ControlTemplate,或者访问 Microsoft Dos: ComboBox Styles and Templates 并从那里复制模板和引用。

要启用文本搜索,请将 ComboBox.IsTextSearchEnabled 设置为 true,并将附加属性 TextSearch.TextPath 设置为项目模型上的实际属性。

出于性能原因,当显示的文本是动态的时候,应该将 Label 替换为 TextBlock

以下是给定的项目模型:

class NetworkAddress : INotifyPropertyChanged
{
  public ImageSource StateImage { get; set; }
  public string Address { get; set; }
}

要启用搜索和项目创建,必须将 ComboBox 配置如下:

<ComboBox ItemsSource="{Binding NetworkAddresses}"
          IsEditable="True"
          IsTextSearchEnabled="True"
          TextSearch.TextPath="Address"
          PreviewKeyUp="ComboBox_PreviewKeyUp">
  <ComboBox.ItemTemplate>
    <DataTemplate DataType="{x:Type NetworkAddress}">
      <WrapPanel>
        <Image Source="{Binding StateImage}" />
        <TextBlock Text="{Binding Address}" />
      </WrapPanel>
    </DataTemplate>
  </ComboBox.ItemTemplate>
</ComboBox>

然后处理 ComboBox.PreviewKeyUp 事件以添加新项目:

partial class MainWindow : Window
{
  // TODO::Property must be a dependency property because MainWindow is a DependencyObject
  public ObservableCollection<NetworkAddress> NetworkAddresses { get; }

  public MainWindow()
  {
    InitializeComponent();

    this.DataContext = this;
    this.NetworkAddresses = new ObservableCollection<NetworkAddress>
    {
      new NetworkAddress() { ... },
      new NetworkAddress() { ... },
    };
  }

  private void ComboBox_PreviewKeyUp(object sender, KeyEventArgs e)
  {
    if (e.Key is not Key.Enter or not Key.Return)
    {
      return;
    }

    var comboBox = sender as ComboBox;
    string inputText = comboBox.Text;
    if (comboBox.IsTextSearchEnabled
        && comboBox.SelectedItem is null
      || !this.NetworkAddresses.Any(address => address.Address.Equals(inputText, StringComparison.OrdinalIgnoreCase)))
    {
      AddNewItemToCollection(inputText);
      comboBox.SelectedIndex = comboBox.Items.Count - 1;

      // Refresh is only required for the ComboBox flyout to render properly
      comboBox.Items.Refresh();
    }
  }

  private void AddNewItemToCollection(string text) 
    => this.NetworkAddresses.Add(new NetworkAddress() { ... });
}
英文:

You are encountering two problems:

  1. Problem: The ComboBox is not containing simple string items. Instead you have defined a DataTemplate to render a more complex item model.
    Solution: The point is that the ComboBox has no chance to know how to use the input value to construct a new instance of the item model. You have to do it explicitly.

  2. Problem: Because your ComboBox is configured to be in edit mode, the TextBox will only contain a text representation of your item model (the ComboBox will call object.ToString on the model to obtain a text representation).
    Solution: There is no out-of-the-box solution. If ComboBox.IsEditable returns false the ComboBox will display the ComboBox.SelectedItem using a ContentPresenter. If ComboBox.IsEditable returns true the ComboBox will replace the ContentPresenter with a TextBox in order to allow the editing. You can now override the original ControlTemplate and toggle between both content sites. For example, on GotFocus you show the TextBox and on LostFocus you switch back to the ContentPresenter.
    You can use the XAML Designer to extract the ControlTemplate or use visit Microsoft Dos: ComboBox Styles and Templates and copy the template and references from there.

To enable text search set ComboBox.IsTextSearchEnabled to true and set the attached property TextSearch.TextPath to the actual property on the item model.

For performance reasons you should replace the Label with a TextBlock, especially when the displayed text is dynamic.

Given is the following item model:

class NetworkAddress : INotifyPropertyChanged
{
  public ImageSource StateImage { get; set; }
  public string Address { get; set; }
}

To enable searching and item creation you must configure the ComboBox as follows:

&lt;ComboBox ItemsSource=&quot;{Binding NetworkAddresses}&quot;
          IsEditable=&quot;True&quot;
          IsTextSearchEnabled=&quot;True&quot;
          TextSearch.TextPath=&quot;Address&quot;
          PreviewKeyUp=&quot;ComboBox_PreviewKeyUp&quot;&gt;
  &lt;ComboBox.ItemTemplate&gt;
    &lt;DataTemplate DataType=&quot;{x:Type NetworkAddress}&quot;&gt;
      &lt;WrapPanel&gt;
        &lt;Image Source=&quot;{Binding StateImage}&quot; /&gt;
        &lt;TextBlock Text=&quot;{Binding Address}&quot; /&gt;
      &lt;/WrapPanel&gt;
    &lt;/DataTemplate&gt;
  &lt;/ComboBox.ItemTemplate&gt;
&lt;/ComboBox&gt;

Then handle the the ComboBox.PreviewKeyUp event to add a new item:

partial class MainWindow : Window
{
  // TODO::Property must be a dependency property because MainWindow is a DependencyObject
  public ObservableCollection&lt;NetworkAddress&gt; NetworkAddresses { get; }

  public MainWindow()
  {
    InitializeComponent();

    this.DataContext = this;
    this.NetworkAddresses = new ObservableCollection&lt;NetworkAddress&gt;
    {
      new NetworkAddress() { ... },
      new NetworkAddress() { ... },
    };
  }

  private void ComboBox_PreviewKeyUp(object sender, KeyEventArgs e)
  {
    if (e.Key is not Key.Enter or not Key.Return)
    {
      return;
    }

    var comboBox = sender as ComboBox;
    string inputText = comboBox.Text;
    if (comboBox.IsTextSearchEnabled
        &amp;&amp; comboBox.SelectedItem is null
      || !this.NetworkAddresse.Any(address =&gt; address.Address.Equals(inputText, StringComparison.OrdinalIgnoreCase)))
    {
      AddNewItemToCollection(inputText);;
      comboBox.SelectedIndex = comboBox.Items.Count - 1;

      // Refresh is only required for the ComboBox flyout to render properly
      comboBox.Items.Refresh();
    }
  }

  private void AddNewItemToCollection(string text) 
    =&gt; this.NetworksAddresses.Add(new NetworkAddress() { ... });

huangapple
  • 本文由 发表于 2023年7月6日 15:25:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/76626416.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定