Search code examples
c#wpfxamlmvvmdatatemplate

Custom WPF Combobox "Application goes to break mode"


I am trying to create a custom combobox by following the example WPF Datagrid as Combobox 2

I downloaded the sample project from the link above. Then installed Extended WPF Toolkit from Nuget and i was able to run the demo. But when i click the down arrow(togglebutton) in the combobox, the application crashes and VS2017 shows break mode.

Below is my ExtendedCombobox class,

public class ExtendedComboBox : ComboBox
{
    #region Dependency Properties

    public static readonly DependencyProperty PopupContentProperty =
        DependencyProperty.Register("PopupContent", typeof(UIElement), typeof(ExtendedComboBox));

    public UIElement PopupContent
    {
        get { return (UIElement)GetValue(PopupContentProperty); }
        set { SetValue(PopupContentProperty, value); }
    }

    #endregion

    #region Constructor

    static ExtendedComboBox()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ExtendedComboBox), new FrameworkPropertyMetadata(typeof(ExtendedComboBox)));
    }

    #endregion
}

Below is the ExtendedDatagridCombobox,

public class ExtendedComboBoxDataGrid : ExtendedComboBox
{
    #region Private Fields

    private readonly DataGrid _dataGrid;

    #endregion

    #region Dependency Properties

    public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns",
        typeof(ObservableCollection<DataGridColumn>), typeof(ExtendedComboBoxDataGrid));

    public ObservableCollection<DataGridColumn> Columns
    {
        get => (ObservableCollection<DataGridColumn>)GetValue(ColumnsProperty);
        set => SetValue(ColumnsProperty, value);
    }

    #endregion

    #region Constructors

    public ExtendedComboBoxDataGrid()
    {
        _dataGrid = new DataGrid
        {
            IsReadOnly = true,
            AutoGenerateColumns = false
        };

        var relativeSource = new RelativeSource(RelativeSourceMode.FindAncestor,
            typeof(ExtendedComboBoxDataGrid), 1);

        // Bind ItemsSource
        var itemsSourceBinding = new Binding("ItemsSource") {RelativeSource = relativeSource};
        _dataGrid.SetBinding(ItemsSourceProperty, itemsSourceBinding);

        // Bind SelectedItem
        Binding selectedItemBinding = new Binding("SelectedItem") {RelativeSource = relativeSource};
        _dataGrid.SetBinding(SelectedItemProperty, selectedItemBinding);

        _dataGrid.SelectionChanged += DataGrid_SelectionChanged;

        PopupContent = _dataGrid;

        Columns = new ObservableCollection<DataGridColumn>();
        Columns.CollectionChanged += Columns_CollectionChanged;
    }

    #endregion

    #region Overrides

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        if (GetTemplateChild("PART_EditableTextBox") is TextBox editableTbx)
        {
            editableTbx.TextChanged += EditableTbx_TextChanged;
        }
    }

    #endregion

    #region Private Methods

    private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        IsDropDownOpen = false;
    }

    private void Columns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach (DataGridColumn column in e.NewItems)
            {
                _dataGrid.Columns.Add(column);
            }
        }

        if (e.OldItems != null)
        {
            foreach (DataGridColumn column in e.OldItems)
            {
                _dataGrid.Columns.Remove(column);
            }
        }
    }

    private void EditableTbx_TextChanged(object sender, TextChangedEventArgs e)
    {
        TextBox editableTbx = (TextBox)sender;
        IsDropDownOpen = true;
        _dataGrid.Items.Filter = o =>
        {
            var properties = o.GetType().GetProperties();
            return properties.Select(p => p.GetValue(o, null)).Any(value => value.ToString().ToLower().Contains(editableTbx.Text));
        };
    }

    #endregion
}

Below is my xaml where i am using the control,

<Window
x:Class="DemoApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CustomControls;assembly=CustomControls"
xmlns:local="clr-namespace:DemoApp"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="MainWindow"
Width="300"
Height="300">
<Grid>
    <Grid.Resources>
        <local:Customers x:Key="Customers" />
    </Grid.Resources>
    <controls:ExtendedComboBoxDataGrid
        Width="250"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        IsEditable="True"
        ItemsSource="{StaticResource Customers}"
        SelectedItem="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
                                                              AncestorType={x:Type Window}},
                               Path=SelectedCustomer}"
        StaysOpenOnEdit="True">
        <controls:ExtendedComboBoxDataGrid.Columns>
            <toolkit:DataGridTextColumn Binding="{Binding Name}" Header="Name" />
            <toolkit:DataGridTextColumn Binding="{Binding Address}" Header="Address" />
            <toolkit:DataGridTextColumn Binding="{Binding TelephoneNumber}" Header="Telephone No." />
        </controls:ExtendedComboBoxDataGrid.Columns>
        <controls:ExtendedComboBoxDataGrid.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Margin="4,0" Text="{Binding Path=Name}" />
                    <TextBlock Margin="4,0" Text="{Binding Path=Address}" />
                    <TextBlock Margin="4,0" Text="{Binding Path=TelephoneNumber}" />
                </StackPanel>
            </DataTemplate>
        </controls:ExtendedComboBoxDataGrid.ItemTemplate>
    </controls:ExtendedComboBoxDataGrid>
</Grid>

And code behind,

 public partial class MainWindow : Window, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private Customer selectedCustomer;

    public Customer SelectedCustomer
    {
        get
        {
            return selectedCustomer;
        }
        set
        {
            if (object.ReferenceEquals(selectedCustomer, value) != true)
            {
                selectedCustomer = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("SelectedCustomer"));
                }
            }
        }
    }

    public MainWindow()
    {
        InitializeComponent();
    }
}

Below is the combobox arrow i click, enter image description here

I get the following error when i click down arrow, Recursive call to Automation Peer API is not valid.'


Solution

  • Apparently there is some issue with WPF Toolkit control Automation property, So i had to override the OnCreateAutomationPeer() method of Datagrid control.

    public class ExtendedDataGrid : DataGrid
    {
        protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
        {
            return null;
        }
    }