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:
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>