Click here to Skip to main content
15,868,016 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I have a WPF application where I use many ComboBox controls. Most of the time I save application user settings in the Settings file, though many people use JSON to do that work but I think the default Settings file in WPF application is provided by Microsoft is far more better to use and robust also.

Now come in to the main topic, suppose if I want to save the Combobox last selected value before my application is closed then I just create a user settings and give the type "string" and declare it in the window closing event.

Here is the example, suppose I create a user settings name "ComboBoxStateThree" and give the type "string".

so, the code will be

Properties.Settings.Default.ComboBoxStateThree = cmb1.SelectedValue.ToString();


and if I want to do through pure XAML then the code of the Binding is

SelectedValuePath="Content" SelectedValue="{Binding Source={x:Static p:Settings.Default}, Path=ComboBoxStateThree, Mode=TwoWay}"


Both methods works great but the XAML approach is too much fast. And another important point is, I save the user Settings file in a separate folder name Properties.



I can save any ComboBox value by those method. But theres a catch. This method only works when the Combobox have predefined static values but if the Combobox itemsource bind to ObservableCollection or any listcollection view (List<>) then it is not working anymore.



One of my Combobox itemsource binded to a List and a added the itemsource from code behind.

Here is some code snippet.
Marshal.ReleaseComObject(pDeviceCollection);
               ListCollectionView lcv = new(devices);
lcv.GroupDescriptions.Add(new PropertyGroupDescription("Direction"));
cmb1.ItemsSource = lcv;


my Combobox name is cmb1 in this case.



My question is, How Can I save the cmb1 selected value in settings file because in this case the ItemSource is the List and the List has different values depending upon situation, in a single sentence the CoboBox has dynamic content.

What I have tried:

I tried a lot of different type of settings like
system.collections.specialized.stringcollection
but none of them works.
Posted
Updated 18-Jan-23 12:23pm
Comments
Richard MacCutchan 18-Jan-23 10:50am    
If the combo contains totally different items next time you use it then you should just ignore the saved value. But why do you want to save a setting that may not exist next time you run the application?
Member 15061773 18-Jan-23 11:38am    
Thank you very much for understanding my problem. Actually, my combo box content are showing available audio devices. May be sometime different type of devices are available that's why combo contains totally different items next time you run. And if there no item changes happen then the previously selected option should be shown in the combo box when I restart my application.

If any item changes happen then the combo box should go to the "System default".
But still, I want to save from a Dynamic content can you please tell me? how can I do that?
Richard MacCutchan 18-Jan-23 11:42am    
You save an item just the same as with anything else. Take a copy of the item's details and save in your settings file.
Member 15061773 18-Jan-23 12:02pm    
But, unfortunately that's not working when Combobox content are dynamic that's why I asking this question in this forum. If you know then please tell me.
Richard MacCutchan 18-Jan-23 12:06pm    
Why not? Wherever the ComboBox items are coming from your code must have access to them. You just need to get them from the container that is the source of the combo.

1 solution

Here is a working example for you...

1. MainWindow XAML:
XML
<Window x:Class="WpfSaveSelectedItemToSettings.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:local="clr-namespace:WpfSaveSelectedItemToSettings"
        mc:Ignorable="d" x:Name="Window"
        Title="MainWindow" Height="450" Width="800"
        Closing="MainWindow_OnClosing">
    <Grid DataContext="{Binding ElementName=Window}"
          HorizontalAlignment="Center"
          VerticalAlignment="Center">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <ComboBox x:Name="AudioDeviceList"
                  ItemsSource="{Binding AudioDevices}"
                  SelectedItem="{Binding SelectedItem}"
                  SelectedIndex="{Binding SelectedIndex}"
                  Width="400"
                  Margin="0 10"/>
        <TextBlock DataContext="{Binding ElementName=AudioDeviceList}"
                   Grid.Row="1">
            <Run Text="Selected Item: " FontWeight="Bold"/>
            <Run Text="{Binding SelectedItem}"></Run>
        </TextBlock>

    </Grid>
</Window>

2. MainWindow Code Behind (part 1 - core code):
C#
public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeComponent();
        InitData();
        LoadSelected();
    }

    private int selectedIndex = -1;

    private const string DeviceKey = "SelectedDevice";

    private readonly Random random = new Random();

    public ObservableCollection<string> AudioDevices { get; } = new();

    public string SelectedItem { get; set; }

    public int SelectedIndex
    {
        get => selectedIndex;
        set => SetField(ref selectedIndex, value);
    }

    private void InitData()
    {
        // simulate discovering of installed/active devices...

        List<string> discoveredDevices = new()
        {
            "Logitech G533 Wireless Gaming Headset",
            "HyperX Cloud II Gaming Headset",
            "Creative Sound BlasterX G5 Portable USB DAC",
            "SteelSeries Arctis 7 Wireless Gaming Headset",
            "Razer Nari Ultimate Wireless Gaming Headset",
            "Astro A50 Wireless Gaming Headset",
            "Bose QuietComfort 35 II Wireless Headphones",
            "Plantronics RIG 800LX Wireless Gaming Headset",
            "JBL Quantum 800 Wireless Gaming Headset",
            "Corsair Virtuoso RGB Wireless SE Gaming Headset",
        };

        // randomize the order of discovery
        discoveredDevices = discoveredDevices.OrderBy(
            x => Guid.NewGuid()).ToList();

        // simulate maximum devices found
        int maxDiscovered = random.Next(3, discoveredDevices.Count);

        for (int i = 0; i < maxDiscovered; i++)
            AudioDevices.Add(discoveredDevices[i]);
    }

    private void MainWindow_OnClosing(object? sender, CancelEventArgs e)
        => SaveSelected();

    private void LoadSelected()
    {
        string? selectedDevice = LoadAppSetting(DeviceKey);

        if (string.IsNullOrWhiteSpace(selectedDevice))
            return;

        // set the last saved device
        SelectedIndex = AudioDevices.IndexOf(selectedDevice);
    }

    private void SaveSelected()
    {
        if (string.IsNullOrWhiteSpace(SelectedItem))
            return;

        SaveAppSetting(DeviceKey, SelectedItem);
    }

Above we have the implementation for managing the selected ComboBox item from saved data.

If you want to save on selection, not on closing of the app, then unhook the MainWindow_OnClosing event and update the SelectedIndex property with the following:
C#
public int SelectedIndex
{
    get => selectedIndex;
    set
    {
        SetField(ref selectedIndex, value);
        SaveSelected();
    }
}


3. MainWindow Code Behind (part 2 - support code):
C#
    #region App.Config Load/Save Implementation

    private string? LoadAppSetting(string key)
    {
        Configuration configFile = LoadConfiguration();

        KeyValueConfigurationCollection? appSettings =
            configFile.AppSettings.Settings;
        
        if (appSettings is null)
            return default;

        return appSettings[key].Value;
    }

    private void SaveAppSetting(string key, string value)
    {
        try
        {
            Configuration configFile = LoadConfiguration();

            KeyValueConfigurationCollection? settings =
                configFile.AppSettings.Settings;

            if (settings[key] == null)
                settings.Add(key, value);
            else
                settings[key].Value = value;

            configFile.Save(ConfigurationSaveMode.Modified);

            ConfigurationManager.RefreshSection(
                configFile.AppSettings.SectionInformation.Name);
        }
        catch (ConfigurationErrorsException)
        {
            // error handler goes here...
            Console.WriteLine("Error saving settings!");
        }
    }

     private static Configuration LoadConfiguration()
    {
        ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
        fileMap.ExeConfigFilename =
            AppDomain.CurrentDomain.BaseDirectory +
            "..\\..\\App.Config";

        Configuration configFile = ConfigurationManager
           .OpenMappedExeConfiguration(fileMap,
               ConfigurationUserLevel.None);

        return configFile;
    }

   #endregion

    #region INotifyPropertyChanged Implementation

    public event PropertyChangedEventHandler? PropertyChanged;

    protected virtual void OnPropertyChanged(
        [CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this,
            new PropertyChangedEventArgs(propertyName));
    }

    protected bool SetField<T>(ref T field, T value,
        [CallerMemberName] string? propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
            return false;

        field = value;
        OnPropertyChanged(propertyName);

        return true;
    }

#endregion
}

This part shows how to load/save from the App.Config file. Also the support for Data Binding.

Enjoy!
 
Share this answer
 
v3

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