How to separate View data and ViewModel data? - c#

I need to manage UI specific parameters (View) and Application data (Model/ViewModel) separately, so I'm using the code-behind of the View for the first, and a separated class (prefixed ViewModel) for the later. This is an simplified version of what I have:
View (XAML)
<Window x:Class="UrSimulator.View.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyView" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding FirstColumnWidth}" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<Label>Width:</Label>
<TextBox Text="{Binding FirstColumnWidth}" IsReadOnly="True" Background="LightGray" />
</StackPanel>
<StackPanel Grid.Column="1">
<Label>First Column Width:</Label>
<TextBox Text="{Binding FirstColumnWidth}" />
<Label>View Model Data:</Label>
<TextBox Text="{Binding MyViewModel.PropertyFromVM}" />
<Label Content="{Binding MyViewModel.PropertyFromVM}" />
</StackPanel>
</Grid>
View (Code behind)
public partial class MyView : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private MyViewModel m_MyViewModel;
public MyViewModel MyViewModel
{
get { return m_MyViewModel; }
set
{
m_MyViewModel = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("MyViewModel"));
}
}
private GridLength m_FirstColumnWidth;
public GridLength FirstColumnWidth
{
get { return m_FirstColumnWidth; }
set
{
m_FirstColumnWidth = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("FirstColumnWidth"));
}
}
public MyView()
{
MyViewModel = new MyViewModel();
DataContext = this;
FirstColumnWidth = new GridLength(100);
InitializeComponent();
}
}
ViewModel
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string m_PropertyFromVM;
public string PropertyFromVM
{
get { return m_PropertyFromVM; }
set
{
m_PropertyFromVM = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("PropertyFromVM"));
}
}
public MyViewModel()
{
PropertyFromVM = "Some business data";
}
}
It works, but I find it cumbersome to use MyViewModel. on every binding that points to the VM.
Questions:
Is there another way to do this without using the prefix?
How should I write the binding for the UI (the width property) if instead of using this for the DataContext, I'd use:
DataContext = MyViewModel;
I'm doing everything wrong and this is not how it is intended to be?
Note: Forget about the converter needed for the Width, it works as long as the text is valid and is not my concern on the question.

DataContext = this;
Yuck... :)
Let the view model be the data context, and bind on your view's properties like this :
<Window x:Name="This" ...>
...
<SomeControl SomeProperty="{Binding MyViewProperty, ElementName=This}"/>
...
</Window>
Side note :
class MyView : Window, INotifyPropertyChanged
Why aren't your view's properties "dependency properties" if you inherit Window ?

This is one way of doing MVVM, but not a great choice as you are still using tightly coupled View objects.
The ideal is where you let WPF infer what View class to use by binding your ViewModel objects to the Content property of ContentPresenters and setting up DataTemplate entries for your ViewModel types.
That way, you don't even need to use a DataContext = blah statement in your code anywhere.
e.g. in the App.xaml or similar
<DataTemplate DataType={x:Type MyViewModel}>
<local:MyViewModelView/>
</DataTemplate>
... then in the Window/UserControl/XAML wherever you need it...
<ContentPresenter Content={Binding MyViewModelAsAProperty}/>
...which can be a DependencyProperty or a standard INotifyPropertyChanged enabled property on another ViewModel.

Add this codes on your MyViewModel Class
private MyView _ObjMyViewModel;
public MyView ObjMyViewModel
{
get { return _ObjMyViewModel; }
set
{
_ObjMyViewModel= value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ObjMyViewModel"));
}
}
And in XAML
<Window.DataContext>
<ViewModel:MyViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ObjMyViewModel.FirstColumnWidth}" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<Label>Width:</Label>
<TextBox Text="{Binding ObjMyViewModel.FirstColumnWidth}" IsReadOnly="True" Background="LightGray" />
</StackPanel>
<StackPanel Grid.Column="1">
<Label>First Column Width:</Label>
<TextBox Text="{Binding ObjMyViewModel.FirstColumnWidth}" />
<Label>View Model Data:</Label>
<TextBox Text="{Binding MyViewModel.PropertyFromVM}" />
<Label Content="{Binding MyViewModel.PropertyFromVM}" />
</StackPanel>
I hope its working..

Related

How to communicate between UserControl in MVVM - WPF Application

I want to create an application with a menu on the left that will change the content on the right.
For that, I Have a MainWindow with two ContentControl (One that will Content a UserControl 'Menu' and the other one that will Content the selected UserControl 'Red' or 'Green'.
The problem is that the Content on the right does not Change...
I have made some research and saw concepts like Dependency Injection, Delegate, Event, Message Bus, ViewModelLocator...
but I don't know which one would be the most suitable in this case and how to implement it. (I don't want to use MVVMLight or any PlugIn like that)
Thx in advance for your interest.
For that I use the MVVM pattern and DataTemplate Binding:
App.xaml
<Application.Resources>
<DataTemplate DataType="{x:Type viewModel:MainViewModel}">
<view:MainWindow />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModelMenu:LeftViewModel}">
<viewMenu:Left />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModelContent:RedViewModel}">
<viewContent:Red />
</DataTemplate>
</Application.Resources>
ViewModel.cs
public abstract class ViewModel : INotifyPropertyChanged
{
#region Properties
private ViewModel _mainContent;
public ViewModel MainContent
{
get => _mainContent;
set
{
_mainContent = value;
OnPropertyChanged();
MessageBox.Show(nameof(MainContent));
}
}
#endregion Properties
public ViewModel()
{
InitCommands();
}
protected abstract void InitCommands();
#region Factory Method - CreateCommand
protected ICommand CreateCommand(Action execute, Func<bool> canExecute)
{
return new RelayCommand(
execute: execute,
canExecute: canExecute
);
}
protected ICommand CreateCommand<T>(Action<T> execute, Predicate<T> canExecute)
{
return new RelayCommand<T>(
execute: execute,
canExecute: canExecute
);
}
#endregion Factory Method - CreateCommand
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainViewModel.cs
internal class MainViewModel : ViewModel
{
private ViewModel _leftMenu;
public ViewModel LeftMenu
{
get => _leftMenu;
set
{
_leftMenu = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
LeftMenu = new LeftViewModel();
}
protected override void InitCommands()
{
}
LeftViewModel.cs
internal class LeftViewModel : ViewModel
{
public ICommand ChangeContentToRed { get; set; }
public ICommand ChangeContentToGreen { get; set; }
protected override void InitCommands()
{
ChangeContentToRed = new RelayCommand(
execute: () => MainContent = new RedViewModel(),
canExecute: () => !(MainContent is RedViewModel)
);
ChangeContentToGreen = new RelayCommand(
execute: () => MainContent = new GreenViewModel(),
canExecute: () => !(MainContent is GreenViewModel)
);
}
}
RedViewModel and GreenViewModel are empty so I don't show the code but extend ViewModel
Window.xaml
<Window.DataContext>
<viewModel:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<ContentControl Grid.Column="0" Content="{Binding Path=LeftMenu}" />
<ContentControl Grid.Column="1" Content="{Binding Path=MainContent}" />
</Grid>
Left.xaml
<UserControl.DataContext>
<viewModel:LeftViewModel />
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button
Grid.Row="0"
Command="{Binding Path=ChangeContentToRed}"
Content="Red" />
<Button
Grid.Row="1"
Command="{Binding Path=ChangeContentToGreen}"
Content="Green" />
</Grid>
Red and Green are only two UserControl with a red and a green grid
When you have a DataTemplate like
<DataTemplate DataType="{x:Type viewModelMenu:LeftViewModel}">
<viewMenu:Left />
</DataTemplate>
and then assign a value of type LeftViewModel to the Content property of a ContentControl, like
<ContentControl Content="{Binding Path=LeftMenu}"/>
the DataTemplate is assigned to the ContentTemplate of the ContentControl, and the element in the instantiated DataTemplate (i.e. your UserControl) inherits the DataContext of the ContentPresenter in the ControlTemplate of the ContentControl, which then holds the Content value.
However, this only works if you do not explicitly assign the UserControl's DataContext and thus break the value inheritance of the DataContext property.
You have to remove the explicit DataContext assignment from the UserControl, i.e. this:
<UserControl.DataContext>
<viewModel:LeftViewModel />
</UserControl.DataContext>

WPF: access ElementName in a list within a list on the XAML side

I'm having a hard time solving a potential newbie problem: I've got a ObservableCollection<TopItem> MyTopItems that I display in a ListView. The type TopItem contains a string TopName and an ObservableCollection<NestedItem> NestedItems. The type NestedItem contains only a string NestedName.
My problematic is quite simple: I want to retrieve information on the nested item that I select, on the XAML side.
Right now, I can retrieve the selected item of TopItems quite easily, but I can't retrieve the selected item of NestedItems.
I know that I can bind the selected item (for TopItems and NestedItems) in the view model, but in my case it's almost pointless because I've got no use for it in the view model. Plus, I'd really like to know how to do it on the XAML side!
Enough talk, now comes the code.
A class to implement to INotifyPropertyChanged interface that I'm gonna use in my models and view model; not the cleanest way of doing, but it's for the sake of the demo. This class is just there to see the big picture, just know that it works well:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfSelectItemInDoubleList.Utils
{
public abstract class INPCBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisedPropertyChanged([CallerMemberName]string propertyName = null)
{
if (this.PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
else
{
storage = value;
RaisedPropertyChanged(propertyName);
return true;
}
}
}
}
Comes the NestedItem type:
using WpfSelectItemInDoubleList.Utils;
namespace WpfSelectItemInDoubleList.Model
{
public class NestedItem : INPCBase
{
private string _NestedName;
public string NestedName
{
get { return this._NestedName; }
set
{
SetProperty(ref this._NestedName, value);
}
}
public NestedItem(string nestedName)
{
NestedName = nestedName;
}
}
}
The TopItem type:
using System.Collections.ObjectModel;
using WpfSelectItemInDoubleList.Utils;
namespace WpfSelectItemInDoubleList.Model
{
public class TopItem : INPCBase
{
private string _TopName;
public string TopName
{
get { return this._TopName; }
set
{
SetProperty(ref this._TopName, value);
}
}
private ObservableCollection<NestedItem> _NestedItems;
public ObservableCollection<NestedItem> NestedItems
{
get { return this._NestedItems; }
set
{
SetProperty(ref this._NestedItems, value);
}
}
public TopItem(string topName)
{
TopName = topName;
}
}
}
The view model:
using System.Collections.ObjectModel;
using WpfSelectItemInDoubleList.Model;
using WpfSelectItemInDoubleList.Utils;
namespace WpfSelectItemInDoubleList.ViewModel
{
public class MainWindowViewModel : INPCBase
{
private ObservableCollection<TopItem> _TopItems;
public ObservableCollection<TopItem> TopItems
{
get { return this._TopItems; }
set
{
SetProperty(ref this._TopItems, value);
}
}
public MainWindowViewModel()
{
TopItems = new ObservableCollection<TopItem>();
for (int i = 0; i < 5; i++)
{
var topItem = new TopItem($"top item {i}")
{
NestedItems = new ObservableCollection<NestedItem>()
};
for (int j = 0; j < 5; j++)
{
var nestedItem = new NestedItem($"NI {j}");
topItem.NestedItems.Add(nestedItem);
}
TopItems.Add(topItem);
}
}
}
}
Finally, the most important part: the XAML!:
<Window x:Class="WpfSelectItemInDoubleList.View.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:WpfSelectItemInDoubleList"
xmlns:vm="clr-namespace:WpfSelectItemInDoubleList.ViewModel"
mc:Ignorable="d"
Title="List in list" Height="350" Width="525">
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<ListView x:Name="TopItemsLV" Grid.Row="0" Margin="10" HorizontalContentAlignment="Stretch" ItemsSource="{Binding TopItems, Mode=TwoWay}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="TopNameTB" Grid.Column="0" Text="{Binding TopName}" TextWrapping="Wrap" VerticalAlignment="Center" />
<StackPanel Grid.Column="1">
<ListView x:Name="NestedItemsLV" ItemsSource="{Binding NestedItems}" BorderThickness="0" HorizontalAlignment="Center" SelectionMode="Single">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Background="Transparent" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NestedName}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ContentControl Grid.Row="1" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ContentControl Margin="10" Grid.Row="1" Content="{Binding ElementName=TopItemsLV, Path=SelectedItem.TopName}" />
<ContentControl Margin="10" Grid.Row="2" Content="{Binding ElementName=NestedItemsLV, Path=SelectedItem.NestedName}" />
</Grid>
</Window>
The interesting part is the second ContentControl. The first one is working well, but the second doesn't: nothing is showing when I select a nested item. A hint is given to me by intellisense: it sees the TopItemsLV, but not the NestedItemsLV.
Prepare for the most beautiful UI ever. Please don't stole it from me, I'm planning to make millions out of it! Just kidding.
As you can see, the selected item from TopItems is showing, but not the selected item from NestedItems. Any idea why?
Thanks :)
EDIT: Skip the first solution. It's more appropriate for really simple views. Scroll down to my second solution instead.
If you are only binding a single ItemsControl (e.g., ListView) to this list of TopItem instances, then you could just the default collection view manage the selected items for you. That's probably the simplest way to do this.
First, set IsSynchronizedWithCurrentItem="True" on both TopItemsLV and NestedItemsLV.
Then, change your content control bindings as follows:
<ContentControl Content="{Binding Path=TopItems/TopName}" />
<ContentControl Content="{Binding Path=TopItems/NestedItems/NestedName}" />
The / separator in a binding path means "drill down into the currently selected item". The selected item is maintained by the default collection view for both your TopItems collection and each NestedItems collection. The default collection view is what you would get if you called CollectionViewSource.GetDefaultView.
Better Solution
The conventional MVVM approach would be to add a SelectedItem property alongside your TopItems and NestedItems collections. Make sure they fire property change events. The property type should match the corresponding collection's element type. If these properties start out with a null value, then nothing will be selected initially, which is what you want.
On both list views, set SelectedItem="{Binding SelectedItem, Mode=TwoWay}". Remove the IsSynchronizedWithCurrentItem settings from my original answer.
Adjust your content control bindings as follows:
<ContentControl Content="{Binding Path=SelectedItem.TopName}" />
<ContentControl Content="{Binding Path=SelectedItem.SelectedItem.NestedName}" />
Attach a new event handler to NestedItemsLV:
<ListView x:Name="NestedItemsLV"
GotFocus="OnNestedItemsLVGotFocus"
... />
In your view's code-behind, implement the handler as follows:
private void OnNestedItemsLVGotFocus(object sender, RoutedEventArgs e)
{
var viewModel = this.DataContext as MainWindowViewModel;
var parentItem = (sender as FrameworkElement)?.DataContext as TopItem;
if (viewModel != null && parentItem != null)
viewModel.SelectedItem = parentItem;
}
I think you'll agree that this solution works better.

How to access objects in code from XAML

I am new to WPF and am trying to understand how to use data binding to bind the controls on my window to objects in my code behind. I see several questions about accessing XAML objects from the codebehind, but that's not what I'm looking for. I already know how to do that.
label1.Content = LabelText;
listbox1.ItemsSource = ListItems;
I have also seen answers about how to access a class in the codebehind from XAML.
<local:MyClass x:Key="myClass" />
But I don't see how to apply that to a specific instance of the class. Here is an example of what I'm trying to do. The 'Bindings' are obviously incorrect. That is what I need help with.
public partial class MainWindow : Window
{
private string _labelText;
private List<string> _listItems = new List<string>();
public MainWindow()
{
InitializeComponent();
_labelText = "Binding";
_listItems.Add("To");
_listItems.Add("An");
_listItems.Add("Object");
}
public string LabelText
{
get { return _labelText; }
set { _labelText = value; }
}
public List<string> ListItems
{
get { return _listItems; }
set { _listItems = value; }
}
}
<Window x:Class="SO_Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SO Demo" Height="160" Width="225">
<Grid DataContext="MainWindow">
<Label x:Name="label1" Width="80" Height="25" Margin="12,12,0,0"
Content="{Binding Path=LabelText}"
HorizontalAlignment="Left" VerticalAlignment="Top" />
<ListBox x:Name="listbox1" Width="100" Height="60" Margin="12,44,0,0"
ItemsSource="{Binding Path=ListItems}" DisplayMemberPath="ListItems"
HorizontalAlignment="Left" VerticalAlignment="Top" />
</Grid>
</Window>
The books and tutorials I have read make it sound like this should be very simple. What am I missing?
While you can DataBind directly to the class in the manner you're attempting, it is not how this is commonly done. The recommended approach is to create an object (ViewModel) that aggregates all the model data you want displayed in your UI, and then set that ViewModel as the DataContext of your View (Window in this case). I would recommend reading about MVVM, which is how most WPF application are built. But the example below can get you started.
Here is a simple example based on your sample above:
ViewModel
public class MyViewModel : INotifyPropertyChanged
{
private string _title;
private ObservableCollection<string> _items;
public string LabelText
{
get { return _title; }
set
{
_title = value;
this.RaisePropertyChanged("Title");
}
}
public ObservableCollection<string> ListItems {
get { return _items; }
set
{
_items = value; //Not the best way to populate your "items", but this is just for demonstration purposes.
this.RaisePropertyChanged("ListItems");
}
}
//Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
CodeBehind
public partial class MainWindow : Window
{
private MyViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MyViewModel();
//Initialize view model with data...
this.DataContext = _viewModel;
}
}
View (Window)
<Window x:Class="SO_Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SO Demo" Height="160" Width="225">
<Grid>
<Label x:Name="label1" Width="80" Height="25" Margin="12,12,0,0" Content="{Binding Path=LabelText}"
HorizontalAlignment="Left" VerticalAlignment="Top" />
<ListBox x:Name="listbox1" Width="100" Height="60" Margin="12,44,0,0"
ItemsSource="{Binding Path=ListItems}"
HorizontalAlignment="Left" VerticalAlignment="Top" />
</Grid>
</Window>
<Grid DataContext="MainWindow"> is invalid.
If you want to reference the window you must either:
<Window x:Name="MyWindow">
<Grid DataContext="{Binding ElementName=MyWindow}"/>
</Window>
or
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>

Binding C# WPF controls to a dictionary

Hi :) I’m just learning C# and WPF and I need to write a tool that:
Loads a data file (string Key, int Value)
Binds the data to a WPF UI for edits
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 :)
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.
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.
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>
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.

WPF Binding ListBox Master/Detail

I can get this working with an XmlDataSource but not with my own classes. All I want to do is bind the listbox to my collection instance and then link the textbox to the listbox so I can edit the person's name (two-way). I've deliberately kept this as simple as possible in the hope that somebody can fill in the blanks.
XAML:
<Window x:Class="WpfListTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfListTest"
Title="Window1" Height="300" Width="600">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0">
<ListBox />
</DockPanel>
<DockPanel Grid.Column="2">
<StackPanel>
<Label>Name</Label>
<TextBox />
</StackPanel>
</DockPanel>
</Grid>
</Window>
C# code behind:
namespace WpfListTest
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public People MyPeeps = new People();
public Window1()
{
InitializeComponent();
MyPeeps.Add(new Person("Fred"));
MyPeeps.Add(new Person("Jack"));
MyPeeps.Add(new Person("Jill"));
}
}
public class Person
{
public string Name { get; set; }
public Person(string newName)
{
Name = newName;
}
}
public class People : List<Person>
{
}
}
All the examples on the web seem to have what is effectively a static class returning code-defined data (like return new Person("blah blah")) rather than my own instance of a collection - in this case MyPeeps. Or maybe I'm not uttering the right search incantation.
One day I might make a sudden breakthrough of understanding this binding stuff but at the moment it's baffling me. Any help appreciated.
The correct way would be to use the MVVM pattern and create a ViewModel like so:
public class MainWindowViewModel : INotifyPropertyChanged
{
private People _myPeeps;
private Person _selectedPerson;
public event PropertyChangedEventHandler PropertyChanged;
public People MyPeeps
{
get { return _myPeeps; }
set
{
if (_myPeeps == value)
{
return;
}
_myPeeps = value;
RaisePropertyChanged("MyPeeps");
}
}
public Person SelectedPerson
{
get { return _selectedPerson; }
set
{
if (_selectedPerson == value)
{
return;
}
_selectedPerson = value;
RaisePropertyChanged("SelectedPerson");
}
}
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Initialize it in your View's code behind like so:
public partial class MainWindow : Window
{
private readonly MainWindowViewModel _viewModel;
public MainWindow()
{
_viewModel = new MainWindowViewModel();
_viewModel.MyPeeps = new People();
_viewModel.MyPeeps.Add(new Person("Fred"));
_viewModel.MyPeeps.Add(new Person("Jack"));
_viewModel.MyPeeps.Add(new Person("Jill"));
DataContext = _viewModel;
InitializeComponent();
}
}
And bind the data like so:
<Window x:Class="WpfApplication3.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">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width="3" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0">
<ListBox SelectedItem="{Binding SelectedPerson}"
DisplayMemberPath="Name"
ItemsSource="{Binding MyPeeps}" />
</DockPanel>
<DockPanel Grid.Column="2">
<StackPanel>
<Label>Name</Label>
<TextBox Text="{Binding SelectedPerson.Name}" />
</StackPanel>
</DockPanel>
</Grid>
</Window>
The binding will work like this:
The DataContext of the window itself is set to the ViewModel instance. Because the ListBox and the TextBox don't specify any DataContext, they inherit it from the Window. The bindings on an object always work relative to the DataContext if nothing else is being specified. That means that the TextBox binding looks for a property SelectedPerson in its DataContext (i.e., in the MainWindowViewModel) and for a Property Name in that SelectedPerson.
The basic mechanics of this sample are as follows:
The SelectedPerson property on the ViewModel is always synchronized with the SelectedItem of the ListBox and the Text property of the TextBox is always synchronized with the Name property of the SelectedPerson.
Try to inherit your People class from ObservableCollection<Person>

Categories