Click here to Skip to main content
15,867,330 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Hi there, I have a WPF application where I use a ComboBox. My ComboBox ItemSource is bind to a List <> which I populate from C# in background.

Here is the C# code of that List<> :

// Main code
          List<AudioDevice> devices = new List<AudioDevice>();
      HRESULT hr = HRESULT.E_FAIL;
      Guid CLSID_MMDeviceEnumerator = new Guid("{BCDE0395-E52F-467C-8E3D-C4579291692E}");
      Type MMDeviceEnumeratorType = Type.GetTypeFromCLSID(CLSID_MMDeviceEnumerator, true);
      object MMDeviceEnumerator = Activator.CreateInstance(MMDeviceEnumeratorType);
      IMMDeviceEnumerator pMMDeviceEnumerator = (IMMDeviceEnumerator)MMDeviceEnumerator;
      if (pMMDeviceEnumerator != null)
      {
          string sIdDefaultRender = null;
          string sIdDefaultCapture = null;
          IMMDevice pDefaultDevice = null;
          hr = pMMDeviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eConsole, out pDefaultDevice);
          if (hr == HRESULT.S_OK)
          {
              IntPtr hGlobal = Marshal.AllocHGlobal(260);
              hr = pDefaultDevice.GetId(out hGlobal);
              sIdDefaultRender = Marshal.PtrToStringUni(hGlobal);
              Marshal.FreeHGlobal(hGlobal);
              Marshal.ReleaseComObject(pDefaultDevice);
          }
          hr = pMMDeviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eCapture, ERole.eConsole, out pDefaultDevice);
          if (hr == HRESULT.S_OK)
          {
              IntPtr hGlobal = Marshal.AllocHGlobal(260);
              hr = pDefaultDevice.GetId(out hGlobal);
              sIdDefaultCapture = Marshal.PtrToStringUni(hGlobal);
              Marshal.FreeHGlobal(hGlobal);
              Marshal.ReleaseComObject(pDefaultDevice);
          }
          IMMDeviceCollection pDeviceCollection = null;
          hr = pMMDeviceEnumerator.EnumAudioEndpoints(EDataFlow.eAll, DEVICE_STATE_ACTIVE | DEVICE_STATE_UNPLUGGED, out pDeviceCollection);
          if (hr == HRESULT.S_OK)
          {
              uint nDevices = 0;
              hr = pDeviceCollection.GetCount(out nDevices);
              devices.Add(new AudioDevice() { Name = "System default", Direction = "Playback", Id = sIdDefaultRender, Default = true });
              for (uint i = 0; i < nDevices; i++)
              {
                  IMMDevice pDevice = null;
                  hr = pDeviceCollection.Item(i, out pDevice);
                  if (hr == HRESULT.S_OK)
                  {
                      IPropertyStore pPropertyStore = null;
                      hr = pDevice.OpenPropertyStore(STGM_READ, out pPropertyStore);
                      if (hr == HRESULT.S_OK)
                      {
                          string sFriendlyName = null;
                          string sDesc = null;
                          PROPVARIANT pv = new PROPVARIANT();
                          hr = pPropertyStore.GetValue(ref PKEY_Device_FriendlyName, out pv);
                          if (hr == HRESULT.S_OK)
                          {
                              sFriendlyName = Marshal.PtrToStringUni(pv.pwszVal);
                          }
                          hr = pPropertyStore.GetValue(ref PKEY_Device_DeviceDesc, out pv);
                          if (hr == HRESULT.S_OK)
                          {
                              sDesc = Marshal.PtrToStringUni(pv.pwszVal);
                          }
                          IntPtr hGlobal = Marshal.AllocHGlobal(260);
                          hr = pDevice.GetId(out hGlobal);
                          string sId = Marshal.PtrToStringUni(hGlobal);
                          Marshal.FreeHGlobal(hGlobal);
                          IMMEndpoint pEndpoint = null;
                          pEndpoint = (IMMEndpoint)pDevice;
                          EDataFlow eDirection = EDataFlow.eAll;
                          hr = pEndpoint.GetDataFlow(out eDirection);
                          //System.Diagnostics.Trace.WriteLine("\tDirection : " + eDirection.ToString());
                          string sDirection = "";
                          if (eDirection == EDataFlow.eRender)
                              sDirection = "Playback";
                          else if (eDirection == EDataFlow.eCapture)
                              sDirection = "Recording";
                          int nState = 0;
                          hr = pDevice.GetState(out nState);
                          if ((nState == DEVICE_STATE_ACTIVE))
                          {
                              devices.Add(new AudioDevice() { Name = sFriendlyName, Direction = sDirection, Id = sId, Default = (sId == sIdDefaultRender || (sId == sIdDefaultCapture)) });
                              //sFriendlyName += (sId == sIdDefaultRender || sId == sIdDefaultCapture) ? " (System default)" : "";
                              //devices.Add(new AudioDevice() { Name = sFriendlyName, Direction = sDirection, Default = (sId == sIdDefaultRender || (sId == sIdDefaultCapture)) });
                              ////devices.Add(new AudioDevice() { Name = sFriendlyName, Direction = sDirection, Default = (sId == sIdDefaultRender || (sId == sIdDefaultCapture)) });
                          }
                          Marshal.ReleaseComObject(pPropertyStore);
                      }
                      Marshal.ReleaseComObject(pDevice);
                  }
              }
              devices.Insert(devices.Count - 0, new AudioDevice() { Name = "Selected application ...", Direction = "Recording", Id = "Idlast", Default = false });
          }
          Marshal.ReleaseComObject(pDeviceCollection);
          ListCollectionView lcv = new ListCollectionView(devices);
          lcv.GroupDescriptions.Add(new PropertyGroupDescription("Direction"));
          this.cmb1.ItemsSource = lcv;
      }
  }
  public class AudioDevice
          {
              public string Name { get; set; }
              public string Direction { get; set; }
              public string Id { get; set; }
              public bool Default { get; set; }
        
          }


If you carefully see the code then You see this line this.cmb1.ItemsSource = lcv; That means the List<> is added as ItemSource of the ComboBox, Now, I have a control template for comboBoxItem, my ComboBoxitem control template have lots of visual customisation and effects according to my choice.

Here is my ComboBoxItem controltemplate :

<Style x:Key="ItemStyleOne" TargetType="{x:Type ComboBoxItem}">
              <Setter Property="SnapsToDevicePixels" Value="True"/>
              <Setter Property="UseLayoutRounding" Value="True"/>
              <Setter Property="RenderOptions.BitmapScalingMode" Value="NearestNeighbor"/>
              <Setter Property="RenderOptions.ClearTypeHint" Value="Enabled"/>
              <Setter Property="Template">
                  <Setter.Value>
                      <ControlTemplate TargetType="{x:Type ComboBoxItem}">
                          <Grid>
                              <Border x:Name="gd" Background="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}},Path=Background}" BorderThickness="0" SnapsToDevicePixels="True" UseLayoutRounding="True" RenderOptions.BitmapScalingMode="NearestNeighbor" RenderOptions.ClearTypeHint="Enabled">
                                  <ContentPresenter
                                  Name="ContentSite" Margin="25, 3, 0, 11" VerticalAlignment="Center">
                                      <ContentPresenter.ContentTemplate>
                                      <DataTemplate>
                                           <TextBlock SnapsToDevicePixels="True" UseLayoutRounding="True" RenderOptions.BitmapScalingMode="HighQuality" RenderOptions.EdgeMode="Aliased">
                                                  
                                                  <Run Text="{Binding Name}"/>
                                              </TextBlock>
                                      </DataTemplate>
                                  </ContentPresenter.ContentTemplate>
                                  </ContentPresenter>
                              </Border>
                              <Rectangle x:Name="Border1" Width="12" Height="10" Margin="-220,-7,0,0"          RenderOptions.BitmapScalingMode="HighQuality" RenderOptions.ClearTypeHint="Auto" SnapsToDevicePixels="True" RenderOptions.EdgeMode="Unspecified">
                                  <Rectangle.Fill>
                                      <DrawingBrush>
                                          <DrawingBrush.Drawing>
                                              <DrawingGroup>
                                                  <DrawingGroup.Children>
                                                      <GeometryDrawing Brush="#d0021b" Geometry="M95.118,21.399L86.601,12.882C85.422,11.703,83.523,11.703,82.344,12.882L38.799,58.854 13.839,33.882C12.648,32.691,10.716,32.691,9.52499999999999,33.882L0.896999999999991,42.516C-0.294000000000009,43.704,-0.294000000000009,45.636,0.896999999999991,46.83L36.396,83.154C37.083,83.844 38.016,84.081 38.913,83.964 39.84,84.102 40.806,83.868 41.517,83.154L95.118,25.659C96.294,24.483,96.294,22.575,95.118,21.399z">
                                                          <GeometryDrawing.Pen>
                                                              <Pen LineJoin="Round" Brush="#d0021b" Thickness="2.5"/>
                                                          </GeometryDrawing.Pen>
                                                      </GeometryDrawing>
                                                  </DrawingGroup.Children>
                                              </DrawingGroup>
                                          </DrawingBrush.Drawing>
                                      </DrawingBrush>
                                  </Rectangle.Fill>
                              </Rectangle>
                          </Grid>
                          <ControlTemplate.Triggers>
                              <Trigger Property="ComboBoxItem.IsMouseOver" Value="True">
                                  <Setter TargetName="gd" Property="Background" Value="#3c3c3c"></Setter>
                                  <Setter TargetName="gd" Property="TextElement.Foreground" Value="#ffffff"></Setter>
                                  <Setter TargetName="gd" Property="SnapsToDevicePixels" Value="True"/>
                                  <Setter TargetName="gd" Property="UseLayoutRounding" Value="True"/>
                                  <Setter TargetName="gd" Property="RenderOptions.BitmapScalingMode" Value="NearestNeighbor"/>
                                  <Setter TargetName="gd" Property="RenderOptions.ClearTypeHint" Value="Enabled"/>
                                  <Setter TargetName="Border1" Property="Visibility" Value="Collapsed"/>
                              </Trigger>
                              <Trigger Property="ComboBoxItem.IsMouseOver" Value="False">
                                  <!--<Setter TargetName="gd" Property="Background" Value="#3c3c3c"></Setter>
                                  <Setter TargetName="gd" Property="TextElement.Foreground" Value="#ffffff"></Setter>
                                  <Setter TargetName="gd" Property="SnapsToDevicePixels" Value="True"/>
                                  <Setter TargetName="gd" Property="UseLayoutRounding" Value="True"/>
                                  <Setter TargetName="gd" Property="RenderOptions.BitmapScalingMode" Value="NearestNeighbor"/>
                                  <Setter TargetName="gd" Property="RenderOptions.ClearTypeHint" Value="Enabled"/>-->
                                  <Setter TargetName="Border1" Property="Visibility" Value="Hidden"/>
                              </Trigger>
                              <Trigger Property="ComboBoxItem.IsSelected" Value="True">
                                  <Setter TargetName="Border1" Property="Visibility" Value="Visible"/>
                                  <!--<Setter TargetName="gd" Property="SnapsToDevicePixels" Value="True"/>
                                  <Setter TargetName="gd" Property="UseLayoutRounding" Value="True"/>
                                  <Setter TargetName="gd" Property="RenderOptions.BitmapScalingMode" Value="NearestNeighbor"/>
                                  <Setter TargetName="gd" Property="RenderOptions.ClearTypeHint" Value="Enabled"/>-->
                              </Trigger>
                          </ControlTemplate.Triggers>
                      </ControlTemplate>
                  </Setter.Value>
              </Setter>
          </Style>


After adding this controltemplate in my ComboBoxitem my comboBox looks amazing. This is how I add this ComboBoxItem Style="{StaticResource ItemStyle}" ,

The code I given, is to show available audio devices in a computer systems.

But in this case my ComboBox content is added from a List<> not from a ComboBox item, all of my code is perfect and I can run successfully.

But my problem is, my ComboBox can not refresh at run time. I give an example, suppose I have three audio devices and I run my Application, my application successfully detect the three devices but If I add more devices at that time, suppose I add two more devices that means total five devices but still my application is showing three devices but it only showing five devices if I restart my application.

How Can I refresh that? I know that, updating and refreshing ComboBox is too much easy, if my Combobox Item source is binded from a ViewModel and if I implement INotifyPropertyChanged event there, but in my case the ComboBox item source is from a List<>. That's why it's too much difficult.

What I have tried:

I tried to implement
INotifyPropertyChanged 
but there only error.
Posted
Updated 31-Dec-22 2:55am

1 solution

For Data Binding[^], you use an ObservableCollection<T>[^] class, not a List.

UPDATE

The trick with Data Binding[^] is not to replace the collection with a new collection, but to clear and add to the collection. Replacing the collection will break the data binding.

UPDATE #2
As mentioned above, your code is not using the correct collection object and is breaking the Data Binding[^]. I have put together an example for you:

1. XAML:
XML
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition Height="Auto"/>
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <ComboBox x:Name="MyComboBox"
              
              Grid.Row="1" Grid.Column="1"
              Width="200">
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}"/>
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox>
    <Button x:Name="AddButton"
            Grid.Row="1" Grid.Column="2"
            Content="Add"
            Margin="10 0 0 0"
            Padding="20 10"
            Click="AddButton_OnClick"/>
    <Button x:Name="ResetButton"
            Grid.Row="1" Grid.Column="3"
            Content="Reset"
            Margin="10 0 0 0"
            Padding="20 10"
            Click="ResetButton_OnClick"/>
    <StackPanel Orientation="Horizontal"
                Grid.Row="1" Grid.Column="4"
                Margin="10 0 0 0"
                VerticalAlignment="Center">
        <TextBlock Text="Count: "
                   FontWeight="Bold"/>
        <TextBlock Margin="10 0 0 0"
                   Text="{Binding ElementName=MyComboBox,
                                  Path=Items.Count,
                                  FallbackValue=0}"/>
    </StackPanel>
</Grid>

2. Code-Behind
C#
public partial class MainWindow : Window
{
    private Random random = new();
    public ObservableCollection<AudioDevice> Devices { get; set; }
    public ListCollectionView  ViewDevices { get; set; }

    public MainWindow()
    {
        InitializeComponent();
        Init();
        InitData();
    }

    private void Init()
    {
        //set once!
        Devices = new();
        ViewDevices = new(Devices);
        ViewDevices.GroupDescriptions.Add(new PropertyGroupDescription("Direction"));

        MyComboBox.ItemsSource = ViewDevices;

    }

    private void InitData()
    {
        for (int i = 0; i < 5; i++)
        {
            AddDevice(Devices.Count + 1);
        }
    }

    private void AddDevice(int id)
    {
        Devices.Add(new()
        {
            Name = $"AudioDevice {id}",
            Direction = $"Dir {random.Next(100)}"
        });
    }


    private void AddButton_OnClick(object sender, RoutedEventArgs e)
    {
        AddDevice(Devices.Count + 1);
    }

    private void ResetButton_OnClick(object sender, RoutedEventArgs e)
    {
        Devices.Clear();
        InitData();
    }
}

public class AudioDevice
{
    public string Name { get; set; }
    public string Direction { get; set; }
    public string Id { get; set; }
    public bool Default { get; set; }
        
}

Notice that:
1. I am using an ObservableCollection<T>[^] and not the List<T> class;
2. I set the binding once, then do not change them;
3. When I update the collection I: a) add to the collection; b) Clear the collection.

Now when I click on buttons, the ComboBox automatically updates via Data Binding[^].

Here is a great resource for learning more: The complete WPF tutorial[^]
 
Share this answer
 
v5
Comments
Member 15061773 31-Dec-22 10:18am    
I already know that, Can you please Complete the full code after applying Observable Collection to implement data binding. So, that I accept your answer.
Graeme_Grant 31-Dec-22 10:52am    
If you already know, then you understand how to change from List to ObservableCollection and it will just work.
Member 15061773 31-Dec-22 11:00am    
Dear sir, sorry I know slightly about ObservableCollection that it's best to use for updating the UI But in this type of situation I hardly have any idea to implement it. Can you please help. I definitely accept your answer.
Graeme_Grant 31-Dec-22 16:55pm    
Are you using Data Binding? Are you using MVVM or Code-Behind?
Member 15061773 1-Jan-23 8:30am    
I use the Code behind method to add Content in combobox in this way.. this.cmb1.ItemsSource = lcv; Just see the first part of my question, where I give the List<> code.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900