Home > front end >  MaterialDesignInXaml ComboBox - Clear button issue
MaterialDesignInXaml ComboBox - Clear button issue

Time:06-09

I'm building WPF application using MaterialDesignInXaml together with ReactiveUI. I have the following XAML code:

<ItemsControl ItemsSource="{Binding Filters}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="{x:Type local:FilterViewModel}">
            <ComboBox
                Margin="6,0,6,12"
                materialDesign:HintAssist.HelperText="{Binding Name}"
                materialDesign:HintAssist.Hint="{Binding Name}"
                materialDesign:TextFieldAssist.HasClearButton="True" /// <--- clear button doesn't work!
                ItemsSource="{Binding Options}"
                SelectedItem="{Binding SelectedOption}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

And the corresponding ViewModel:

public class MainWindowViewModel : ReactiveObject
{
    public List<FilterViewModel> Filters { get; set; } = new();

    public MainWindowViewModel()
    {
        Filters = Enumerable.Range(1, 3).Select(x => new FilterViewModel()
        {
            Name = $"Filter {x}",
            Options = new ObservableCollection<string>(Enumerable.Range(1, 5).Select(y => y.ToString()))
        }).ToList();
    }
}

public class FilterViewModel : ReactiveObject
{
    public ObservableCollection<string> Options { get; set; } = new();

    public string Name
    {
        get => _name;
        set => this.RaiseAndSetIfChanged(ref _name, value);
    }

    public string SelectedOption
    {
        get => _selectedOption;
        set => this.RaiseAndSetIfChanged(ref _selectedOption, value);
    }

    private string _selectedOption;
    private string _name;
}

This gives me nice three ComboBoxes:

Three ComboBoxes with hint text aligned horizontally where the ComboBox in the middle shows "2" with a "X" clear button to the right.

The problem is that ComboBox clear button has no effect when clicked. It simply dosn't do anything, no error, nothing. Am I missing something here? Why it doesn't work?

CodePudding user response:

This is a bug in MaterialDesign tracked here on GitHub:

The issue is in the ClearText.OnClearCommand method, which is executed for the ClearCommand that is bound to the clear button. There it is assumed that the Source of the event is the ComboBox, but it is the button itself, so the method does not clear anything.

Unfortunately, the ClearCommand is hard-wired in the control template, see here:

<Button
   x:Name="PART_ClearButton"
   Grid.Column="2"
   Height="Auto"
   Padding="2,0,0,0"
   Command="{x:Static internal:ClearText.ClearCommand}"
   Focusable="False"
   Style="{StaticResource MaterialDesignToolButton}">

Consequently, a fix of the clear command would require to copy the ClearText class, fix the bug, copy all relevant styles for ComboBox and use the local fixed version of the command in the control template.


Bonus round: You can work around this in code, but it is a more or less dirty fix. You can search for the clear button when the ComboBox is loaded and reassign a fixed version of the command.

I use the Microsoft.Xaml.Behaviors.Wpf NuGet package to encapsulate this fix into a reusable component that can be easily integrated, but you could also use code-behind instead.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using MaterialDesignThemes.Wpf.Internal;
using Microsoft.Xaml.Behaviors;

namespace SignumsAwesomeApp
{
   public class MaterialDesignComboBoxClearButtonFixBehavior: Behavior<ComboBox>
   {
      private static readonly RoutedCommand ClearCommand = new RoutedCommand();

      protected override void OnAttached()
      {
         base.OnAttached();
         AssociatedObject.Loaded  = onl oaded;
      }

      protected override void OnDetaching()
      {
         base.OnDetaching();
         AssociatedObject.Loaded -= onl oaded;
      }

      private void onl oaded(object sender, RoutedEventArgs e)
      {
         if (!ClearText.GetHandlesClearCommand(AssociatedObject))
            return;

         AssociatedObject.CommandBindings.Add(new CommandBinding(ClearCommand, OnClearCommand));
         FindChild<Button>(AssociatedObject, "PART_ClearButton").Command = ClearCommand;
      }

      private void OnClearCommand(object sender, ExecutedRoutedEventArgs e)
      {
         AssociatedObject.SetCurrentValue(ComboBox.TextProperty, null);
         AssociatedObject.SetCurrentValue(Selector.SelectedItemProperty, null);
      }

      private static T FindChild<T>(DependencyObject parent, string childName)
         where T : DependencyObject
      {
         if (parent == null)
            return null;

         T foundChild = null;

         var childrenCount = VisualTreeHelper.GetChildrenCount(parent);
         for (var i = 0; i < childrenCount; i  )
         {
            var child = VisualTreeHelper.GetChild(parent, i);
            if (!(child is T childType))
            {
               foundChild = FindChild<T>(child, childName);

               if (foundChild != null) break;
            }
            else if (!string.IsNullOrEmpty(childName))
            {
               if (childType is FrameworkElement frameworkElement && frameworkElement.Name == childName)
               {
                  foundChild = childType;
                  break;
               }
            }
            else
            {
               foundChild = childType;
               break;
            }
         }

         return foundChild;
      }
   }
}

The FindChild method is from @CrimsonX answer on this question:

You have to add the behavior to your ComboBox in XAML like this:

<ComboBox ...>
   <b:Interaction.Behaviors>
      <local:MaterialDesignComboBoxClearButtonFixBehavior/>
   </b:Interaction.Behaviors>
</ComboBox>
  • Related