WPF Material Design ComboBox Flyout issue

Viewed 5038

I'm using MaterialDesignThemes 2.6.0 in a WPF Framework 4.6.1 application.

If I click on a ComboBox within a StackPanel, the flyout appears without transparency at the first click:

StackPanel flyout.

If I click on a Combobox within a DataGrid, the flyout appears transparent:

GridView first click

I've to click again to show it correctly:

GridView second click

App.xaml:

<Application x:Class="WpfApp1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApp1"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Dark.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.Blue.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.DeepPurple.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

MainWindow.xaml:

<Window x:Class="WpfApp1.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:vm="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Background="{DynamicResource MaterialDesignPaper}"
        TextElement.Foreground="{DynamicResource MaterialDesignBody}"
      Title="MainWindow" Height="250" Width="400">
    <Window.DataContext>
        <vm:ViewModel/>
    </Window.DataContext>
    <StackPanel>
        <ComboBox ItemsSource="{Binding ComboItems}"/>
        <DataGrid ItemsSource="{Binding ComboItems}"
                  AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="Combo" Width="200">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
                                                            Path = DataContext.ComboItems}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </StackPanel>
</Window>

ViewModel.cs:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace WpfApp1
{
    public class ViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<string> _comboItems;

        public ObservableCollection<string> ComboItems
        {
            get => _comboItems;
            set
            {
                if (value == _comboItems) return;
                _comboItems = value;
                OnPropertyChanged();
            }
        }

        public ViewModel()
        {
            _comboItems = new ObservableCollection<string>()
            {
                "Item 1",
                "Item 2"
            };
        }

        #region INotify support
        public event PropertyChangedEventHandler PropertyChanged;

        void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        #endregion
    }
}

I would like to avoid the transparent flyout on first click. Any help appreciated.

EDIT: I opened an issue on github

2 Answers

I too had this issue. I think this is a bug happening because of handling transparency with the background of other area. I found a solution (hack actually ;-P) for this and made an util class by making the combobox background to be opaque on dropdown open and back to transparent on close.

Update :

A delay of 100ms is added without which it is not working properly.

using System;
using System.Windows.Controls;
using System.Windows.Media;
using System.Threading.Tasks;

namespace LogisticsNote.Utils
{
public class ManageCombo
{
    private static Brush OPAQUE_BACKGROUND_ON_OPEN = Brushes.WhiteSmoke;

    public static void SetBackgroundManagementEvents(params ComboBox[] combo)
    {
        foreach (ComboBox c in combo) SetBackgroundManagementEvents(c);
    }

    public static void SetBackgroundManagementEvents(ComboBox combo)
    {
        combo.DropDownOpened += Combo_DropDownOpened;
        combo.DropDownClosed += (s, e) =>
            (s as ComboBox).Background = Brushes.Transparent;
    }

    private static void Combo_DropDownOpened(object sender, EventArgs e)
    {
        var combo = sender as ComboBox;
        combo.IsDropDownOpen = false;
        if (combo.Items.Count == 0)
        {
            combo.Background = Brushes.Transparent;
            return;
        }

        combo.DropDownOpened -= Combo_DropDownOpened;
        combo.Background = OPAQUE_BACKGROUND_ON_OPEN;
        Task.Delay(100).ContinueWith(_ =>
        {
            combo.Dispatcher.Invoke((Action)(() =>
            {
                combo.IsDropDownOpen = true;
                combo.DropDownOpened += Combo_DropDownOpened;
            }));
        });
    }
}
}

I have also made it to not open the dropdown when no items present. This is because of a weird behaviour of the combobox when no items in it.

You can use the class like

ManageCombo.SetBackgroundManagementEvents ( 
    combo1, combo2,      // Set to all combos in UI
    combo3, combo4
);
Related