0

I am new to creating apps. I am trying to build a WPF app that requires me to have a combobox with hundreds of items. I have all of these items saved in a txt/excel file. Obviously, I don't want to hardcode all the options in XAML/C#.

I have no idea how to go about this. I tried to store the list as a resource but don't know how to access the resource once I've put it in there.

I am looking for the easiest approach that can accomplish what I need.

Thanks!

1 Answers1

0

To get the values in your txt/csvfile is trivial (What's the fastest way to read a text file line-by-line?). To get these values to update your ComboBox's contents is actually fairly involved, but the process is at the heart of WPF and MVVM.

The basic idea is to bind an ObservableCollection<string> object to the ItemSource property in your ComboBox and fill it with the items in your text file. I read the file in the constructor of my view model (more on that below) and put all the lines in the Collection (which is bound to the box) on startup. You can also do this elsewhere dynamically if need be.

The usual way to wire it up to the GUI is to use a DataContext in your MainWindow.xaml file. Typically this is done using the Model View ViewModel (MVVM) pattern. The ViewModel is responsible for communicating between the business logic (Model) and the GUI (View). In WPF this is done by having the ViewModel handle event changes in the GUI and also notify the GUI when the data from the model changes. Here I do this with a class called Notifier that implements INotifyPropertyChanged. Then, ViewModel inherits this class and can talk to the GUI via bindings in the xaml. There are other ways of doing the View notifying in WPF/MVVM, but I find this is the simplest. Every WPF project I work on has this class in it.

You will also need bindings for the SelectedItem and SelectedIndex properties in your ComboBox. By binding these to properties that notify in your view model (via calling Update in the setter) you can control all the behavior you need.

Note that I had to remove the StartupUri="MainWindow.xaml" line in the App.xaml file because I instantiate the MainWindow object in the code behind.

Here is how I did it using the standard Visual Stduio WPF application template. Just to clarify how this conforms to MVVM: the 'View' is the GUI itself (defined in MainWindow.xaml), the 'ViewModel' is the ViewModel class, and the 'Model is trivial. It is just a static set of items from the text file to shove into the combobox.

App.xaml:

<Application x:Class="ComboBoxDemo.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:ComboBoxDemo">
    <Application.Resources>
         
    </Application.Resources>
</Application>

App.xaml.cs:

using System.Windows;

namespace ComboBoxDemo
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            ViewModel viewModel = new();
            MainWindow mainWindow = new() { DataContext = viewModel };
            mainWindow.Show();
        }
    }
}

MainWindow.xaml:

<Window x:Class="ComboBoxDemo.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:ComboBoxDemo"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance local:ViewModel}" 
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <ComboBox Grid.Column="0" 
                            ItemsSource="{Binding Items, Mode=OneWay}"
                            SelectedItem="{Binding SelectedItem, Mode=OneWay}"
                            SelectedIndex="{Binding ItemIndex}"
                            Margin="5"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;

namespace ComboBoxDemo
{
    public partial class MainWindow : Window
    {
        public MainWindow() => InitializeComponent();
    }
}

ViewModel.cs

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;

namespace ComboBoxDemo
{

    public abstract class Notifier : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged = null;

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

            field = value;
            OnPropertyChanged(propertyName);
        }

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

    public class ViewModel : Notifier
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>

        // event handling for gui/business logic


        // private fields for bound variables
        private string _selectedItem = "";
        private int _itemIndex = 0;

        // properties for binding to GUI
        public ObservableCollection<string> Items { get; } = new();
        public string SelectedItem
        {
            get => _selectedItem;
            set => Update(ref _selectedItem, value);
        }
        public int ItemIndex
        {
            get => _itemIndex;
            set
            {
                Update(ref _itemIndex, value);
                // here you can use the index to affect business logic as well
            }
        }
        public ViewModel()
        {
            Items = new();
            string textFile = "items.txt";
            using StreamReader file = new StreamReader(textFile);
            string? line;
            while ((line = file.ReadLine()) is not null)
                Items.Add(line);
        }
    }
}

and my combobox items come from 'items.txt' which I put in the same directory as the .exe:

item1
item2
item3

Screenshot of output: enter image description here

dmedine
  • 1,430
  • 8
  • 25
  • saving and reading txt files is not "a fairly complicated thing" and doesn't require mvvm. File.ReadAllLines() and File.WriteAllLines() do the job – ASh Mar 09 '22 at 08:59
  • @ASh But the OP asked how to get the values into a combobox---which (as the amount of code required to demonstrate this process attests to) is a complicated thing. The file read is indeed simple. I will remove that comment from the answer. – dmedine Mar 09 '22 at 20:13