Danny Maher
Danny Maher

Reputation: 173

Binding C# WPF controls to a dictionary

Hi :) I’m just learning C# and WPF and I need to write a tool that:

  1. Loads a data file (string Key, int Value)
  2. Binds the data to a WPF UI for edits
  3. Saves a new data file

My thought was a dictionary would be best. The data is only loaded once from file, and changes are only made with the WPF controls. I’ve tried many things but I still keep hitting road blocks when I bind data to the controls. I’ve been working with a simplified version of the tool – below. The data binds to the WPF control – but there is no change event to update the dictionary. I haven’t found a good example to follow. Could someone explain how to get the dictionary to update? And is the strategy the right one? - using a dictionary -using DataContext. If you'd like to see the full project and UI - there is a link at the bottom. I've been working many-many days...with progress but I'm way too slow ;) Cheers Danny

MainWindow.xaml

<Window x:Class="configGen.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="200" Width="150">
<StackPanel Margin="20" Width="80">
    <TextBox Text="{Binding [item1]}" />
    <TextBlock Text="{Binding [item1]}" />
    <TextBox Text="{Binding [item2]}" />
    <TextBlock Text="{Binding [item2]}" />
    <TextBox Text="{Binding [item3]}" />
    <TextBlock Text="{Binding [item3]}" />
    <Slider Value="{Binding [item4]}" Minimum="0" Maximum="256" />
    <TextBlock Text="{Binding [item4]}" />
</StackPanel>    

MainWindow.xaml.cs

namespace configGen
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            dataClass record = new dataClass();
            DataContext = record.generate();
        }
    }

    public class dataClass
    {
        public Dictionary<string, int> generate()
        {
            Dictionary<string, int> _data = new Dictionary<string, int>();
            _data.Add("item1", 100);
            _data.Add("item2", 120);
            _data.Add("item3", 140);
            _data.Add("item4", 160);
            return _data;
        }
    }
}

Link to full project...

http://www.baytower.ca/btsRV7config.zip


Thanks for all the great feedback everyone!! I will set back to work :)

Upvotes: 1

Views: 8372

Answers (4)

Danny Maher
Danny Maher

Reputation: 173

I've tried tsell's example, using his class in a list. The list is just a convenient way to generate and manage a fixed number of elements. The Item1 WPF control binds to the Item1 object and its value. The object is found by its index number. The binding and dataContext in this case is simple enough for me to use (as a beginner). It works, but I'm not sure it's exactly an elegant way to do it.

public MainWindow()
{
    MyObjects = new List<MyObject>();

    MyObject item1 = new MyObject();
    item1.MyValue = string.Format("100");
    MyObjects.Add(item1);

    MyObject item2 = new MyObject();
    item2.MyValue = string.Format("120");
    MyObjects.Add(item2);

    MyObject item3 = new MyObject();
    item3.MyValue = string.Format("140");
    MyObjects.Add(item3);

    MyObject item4 = new MyObject();
    item4.MyValue = string.Format("160");
    MyObjects.Add(item4);

    InitializeComponent();
    DataContext = this;
}

xaml

<Window x:Class="WpfApplication4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel Margin="20" Width="80">
        <TextBox Text="{Binding MyObjects[0].MyValue}" />
        <TextBlock Text="{Binding MyObjects[0].MyValue}" />
        <TextBox Text="{Binding MyObjects[1].MyValue}" />
        <TextBlock Text="{Binding MyObjects[1].MyValue}" />
        <TextBox Text="{Binding MyObjects[2].MyValue}" />
        <TextBlock Text="{Binding MyObjects[2].MyValue}" />
        <Slider Value="{Binding MyObjects[3].MyValue}" Minimum="0" Maximum="256" />
        <TextBlock Text="{Binding MyObjects[3].MyValue}" />
    </StackPanel>
</Window>

BTW, I will change to int for MyValues..they are all int numbers. For now it is a string.

Upvotes: 0

tsells
tsells

Reputation: 2771

Here is an example of how I would do it.

Main Class (Code Behind)

 /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private List<MyObject> _myObjects;
        public List<MyObject> MyObjects
        {
            get { return _myObjects; }
            set 
            {
                _myObjects = value;
                if(PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("MyObjects"));
                }
            }
        }


    public MainWindow()
    {
        MyObjects = new List<MyObject>();

        // Add 20 records for sample data
        for (int i = 0; i < 20; i++)
        {
            MyObject o = new MyObject();
            o.Label = string.Format("Key{0}", i);
            o.MyValue = string.Format("Value{0}", i);    
            MyObjects.Add(o);
        }
        InitializeComponent();
        DataContext = this;
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Secondary Class

public class MyObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private string _label;
    public string Label
    {
        get { return _label; }
        set
        {
            _label = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Label"));
            }
        }
    }

    private string _myValue;
    public string MyValue
    {
        get
        {
            return _myValue;
        }
        set
        {
            _myValue = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("MyValue"));
            }
        }
    }
}

XAML File

<Window x:Class="WpfApplication4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate x:Key="listboxstyle">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding Path=Label}" />
                <TextBox Grid.Column="1" Text="{Binding Path=MyValue}" />
            </Grid>
        </DataTemplate>
    </Window.Resources>
    <Grid>       

        <ListBox
            ItemsSource="{Binding Path=MyObjects}"
            ItemTemplate="{StaticResource listboxstyle}"
            />

    </Grid>
</Window>

Upvotes: 1

Anas Karkoukli
Anas Karkoukli

Reputation: 1342

A dictionary is definitly not a convenient way to do a two way data binding in WPF. It seems an ObservableCollection is more suited to your requirements.

Something like:

public class ItemsList : ObservableCollection<Item>
{
    public ItemsList() : base()
    {
        Add(new Item("item 1", 100));
        Add(new Item("item 2", 120));
        Add(new Item("item 3", 140));
        Add(new Item("item 4", 160));
    }
  }

Item is a simple class with a name and a value properties. I have ommitted it here because it is self explanatory.

The advantage here is that you can bind to a dynamic number of items not only the ones declared imperatively.

Once you bind you datacontext to it, you get the automatic property notification for two way databinding.

Your XAML will have to change to accomodate binding to a collection of course. Maybe an ItemsControl that takes that collection as its ItemsSource.

Upvotes: 1

RandomEngy
RandomEngy

Reputation: 15413

Instead of using a Dictionary as your DataContext I'd create a custom object like MainViewModel. Give it properties that correspond to item1, item2, etc, except give them appropriate names. Then use <TextBox Text="{Binding MyPropertyName}" />. To handle updates, you can either set your DataContext to a new MainViewModel object or you can set up your class to broadcast property changes. You can do that either through INotifyPropertyChanged on the class or with dependency properties.

At least that's what it seems like you're trying to accomplish. If you're going for displaying an arbitrary number of controls you'd need something different.

Upvotes: 1

Related Questions