TwoWay Binding of a ComboBox to a static property - wpf

-------EDIT------
So, i figured that my code is correct and so are the code snippets from all of your answers. Thanks for that. My problem is that my dev-maschine runs .NET4.5 which behaves differently! The very same program (compiled against .NET4.0) runs correct on a maschine with .NET4.0 but not on a maschine with .NET4.5!
So here is my revised question.
-------EDIT------
First, the simple example how i two-way bind my combobox to my data context:
View model:
public class MainWindowViewModel
{
public List<String> MyElements { get; set; }
public string SelectedElement { get; set; }
public MainWindowViewModel()
{
MyElements = new List<string>() {"a", "b", "c"};
SelectedElement = "a";
}
}
and code-behind
private readonly MainWindowViewModel _viewModel = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _viewModel;
}
and my xaml
<ComboBox
ItemsSource="{Binding MyElements, Mode=OneWay}"
SelectedItem="{Binding SelectedElement}" />
This works fine and if i select an item whith the combobox, it is bound to my view model.
OK, now i want to make my viewModel static but still two-way bind the selectedItem. I try this:
public class MainWindowViewModel
{
public static List<String> MyElements { get; set; }
public static string SelectedElement { get; set; }
static MainWindowViewModel()
{
MyElements = new List<string>() {"a", "b", "c"};
SelectedElement = "a";
}
}
I do not need to set the datacontext in the Code-behind anymore and i know, that xaml needs an instance for two-way binding, so i have still the default constructor. I then bind the combobox
<Window.Resources>
<me:MainWindowViewModel x:Key="model"/>
</Window.Resources>
<StackPanel>
<ComboBox
ItemsSource="{Binding Source={x:Static me:MainWindowViewModel.MyElements}, Mode=OneWay}"
SelectedItem="{Binding Source={StaticResource model}, Path=SelectedElement}" />
</StackPanel>
The initial value is properly bound, but if i select another item with the combobox it it not reflected in my viewModel. What am i doing wrong?
EDIT:
If I use the exact same binding string for a TextBox and change the text in the box, it is reflected in the property.
<TextBox Text="{Binding Source={StaticResource model}, Path=SelectedElement}"/>
So obviously my binding string is correct but the way i use the combobox seems to be wrong. I also tried to bind SelectedValue instead... no change either.

Checked just in a sample project, works fine
public class ViewModel
{
static ViewModel()
{
Items=new ObservableCollection<string>();
SelectedItem = "222";
Items.Add("111");
Items.Add("222");
Items.Add("333");
Items.Add("444");
Items.Add("555");
}
private static string _selectedItem;
public static string SelectedItem
{
get { return _selectedItem; }
set { _selectedItem = value;
MessageBox.Show("Item " + value + " was selected");
}
}
private static ObservableCollection<string> _items;
public static ObservableCollection<string> Items
{
get { return _items; }
set { _items = value; }
}
}
and xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:my="clr-namespace:WpfApplication1"
Title="MainWindow" Height="200" Width="300">
<Grid>
<Grid.Resources>
<my:ViewModel x:Key="viewM"/>
</Grid.Resources>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="101,12,0,0" Name="comboBox1" VerticalAlignment="Top" Width="146"
ItemsSource="{Binding Source={x:Static my:ViewModel.Items}, Mode=OneWay}"
SelectedItem="{Binding Source={StaticResource viewM}, Path=SelectedItem}" />
</Grid>
</Window>
I have uploaded sample.

You're being confused between instances and static properties: you don't need to bind a static object.
<ComboBox
ItemsSource="{x:Static me:MainWindowViewModel.MyElements}"
SelectedItem="{x:Static me:MainWindowViewModel.SelectedElement}" />
And you should implement INotifyPropertyChanged nevertheless.
Binding is about resolving the right instance from which you want to fetch data.
If there is no meaning of instance, there is no need for binding.

Related

Data Binding doesn't work in xaml

I try to use binding to display Hi in the Text content.
However, when clicking the button, it doesn't work.
Could someone help me to solve the problem?
Thanks.
1.XAML CODE :
<Window x:Class="Wpftest.binding.Window0"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window0" Height="300" Width="300">
<Grid>
<TextBox x:Name="textBox2" VerticalAlignment="Top" Width="168"
Text="{Binding Source= stu, Path= Name, Mode=TwoWay}"/>
</Grid>
</Window>
2.Class :
namespace Wpftest.binding.Model
{
public class student : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name
{
get { return name; }
set { name = value;
if(this.PropertyChanged != null)
{
this.PropertyChanged.Invoke(this, new
PropertyChangedEventArgs("Name"));
}
}
}
}
}
3.XAML.cs:
namespace Wpftest.binding
{
public partial class Window0 : Window
{
student stu;
public Window0()
{
InitializeComponent();
stu = new student();
}
private void button_Click(object sender, RoutedEventArgs e)
{
stu.Name += "Hi!";
}
}
}
There are many ways to achieve what you need; the correct method depends very much on what style of application you want to create. I'll demonstrate two methods that will require minimal changes from your supplied example:
Method 1
Set the DataContext to stu and bind to the Name property.
XAML.cs
private student stu;
public Window0()
{
InitializeComponent();
stu = new student();
DataContext = stu;
}
XAML code
<TextBox Text="{Binding Path=Name, Mode=TwoWay}"/>
Method 2
Generally you will set the DataContext to some object other than the Window (e.g. the ViewModel if you are following the MVVM pattern), but sometimes you may need to bind a control to some property of the Window. In this case the DataContext can't be used, but you can still bind to a property of the Window by using RelativeSource. See below:
XAML.cs
// note this must be a property, not a field
public student stu { get; set; }
public Window0()
{
InitializeComponent();
stu = new student();
}
XAML code
<TextBox Text="{Binding Path=stu.Name, Mode=TwoWay,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
Hint: if you are having trouble with WPF data binding, then it often helps to look at the debugger output window to see the binding trace messages. And debugging can be further enhanced by adding this namespace to the Window element
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
and then setting the TraceLevel e.g.
<TextBox Text="{Binding Source=stu, diag:PresentationTraceSources.TraceLevel=High}"/>
Basically you need to set DataContext property to your Window.
For example:
public MainWindow()
{
DataContext=new YourViewModel();
}
DataContext of Window is a way to communicate between View(XAML) and ViewModel(C# code)
In addition, you can add DataContext in xaml:
<Window.DataContext>
<local:YourViewModel/>
</Window.DataContext>
Also, instead of handling Click event, you should use Command property of Button. Example can be seen here.

WPF binding DataGrid to CollectionViewSource: working via DataContext, empty via ItemsSource; difference?

Consider the following XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:WpfApplication1"
DataContext="{Binding Source={x:Static c:ViewModel.Instance}}"
>
<Grid>
<DataGrid DataContext="{Binding ItemsViewSource}" ItemsSource="{Binding}" />
</Grid>
and the view model:
public class ItemViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class ViewModel
{
public static ViewModel Instance { get; set; }
static ViewModel()
{
Instance = new ViewModel();
}
public ObservableCollection<ItemViewModel> Items { get; private set; }
public CollectionViewSource ItemsViewSource { get; private set; }
public ViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
ItemsViewSource = new CollectionViewSource() { Source = Items };
Items.Add(new ItemViewModel() { FirstName = "test", LastName = "test" });
}
}
This code is working, but if I change
<DataGrid DataContext="{Binding ItemsViewSource}" ItemsSource="{Binding}" />
to
<DataGrid ItemsSource="{Binding ItemsViewSource}" />`
DataGrid does not bind, it is empty.
What is the difference between these two bindings?
First of all for second option to work, you need to bind with CollectionViewSource.View property.
<DataGrid ItemsSource="{Binding ItemsViewSource.View}" />
When you bind DataContext of dataGrid to CollectionViewSource, dataGrid internally do value conversion from CollectionViewSource to ICollectionView (ListCollectionView in your case) but when you asked it explicitly to bind to CollectionViewSource, it doesn't do default value conversion. You can verify this by putting converter in your binding.

Bind a class member to a listbox

I am a beginner in wpf. In one of my projects I have a frmField class as bellow:
public class frmFields
{
public bool succelssfulEnd = true;
public string fileName;
public List<string> errList = new List<string>();
}
and a list
<ListBox Name="LSTErrors" Grid.Row="11" Grid.Column="1" Grid.ColumnSpan="2" ItemsSource ="{Binding}" SelectionChanged="LSTErrors_SelectionChanged" />
I need to bind errList to the above listBox in such a way that any change in the List is reflected on the ListBox.
Is there anyone to guide me?
You need an ObservableCollection to refresh the UI every time it changes.
public class frmFields
{
public bool succelssfulEnd = true;
public string fileName;
private ObservableCollection<String> _errList = new ObservableCollection<String>();
public ObservableCollection<String> ErrList {
get {return _errList;}
set {
_errList = value;
//your property changed if you need one
}
}
}
Note that you can only bind to a Property or a DependencyProperty, not a simple public variable like you tried.
In your constructor you'll need to add:
this.DataContext = this;
or in your XAML:
DataContext="{Binding RelativeSource={RelativeSource self}}"
In your root element. And then bind ItemsSource like this:
<ListBox ItemsSource="{Binding ErrList, Mode=OneWay}">
...
</ListBox>

Binding to Property of a ComboBox's SelectedItem

I am having a hard time finding the right syntax for binding to a ComboBox's SelectedItem's property. This is the XAML I am trying to use for the binding. Where you see SelectedItem.Mode is the idea I am having difficulty with. Note that CurrentMode is in the ViewModel and has the same type as SelectedItem.Mode
<ComboBox SelectedItem.Mode="{Binding Path=CurrentMode, Mode=TwoWays}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ImageSource}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<local:ModeItem Mode="Free" ImageSource="pencil.png"/>
<local:ModeItem Mode="Arrow" ImageSource="arrow.png"/>
</ComboBox>
A local:ModeItem looks like this
public class ModeItem : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty ModeProperty = DependencyProperty.Register("Mode", typeof(AnnotationMode), typeof(ModeItem));
public AnnotationMode Mode
{
get { return (AnnotationMode)GetValue(ModeProperty); }
set { SetValue(ModeProperty, value); }
}
public string ImageSource { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
I am using MVVM and trying to bind the AnnotationMode (CurrentMode) of the ViewModel to that of the ComboBox's SelectedItem's AnnotationMode (Mode)
Just do this
SelectedItem="{Binding CurrentMode}
You don't have to do all this extra stuff you are doing. Note You do need to make the datacontext of the combobox is pointing to your viewmodel.
Edit :-
You should be able to do this
SelectedValue="{Binding CurrentMode, Mode=TwoWay}"
SelectedValuePath="Mode"

ComboBox wpf not item not being selected

I am trying to bind a combo box to a list of objects, and it works great, besides the selected value, am I missing somethign?
<ComboBox ItemsSource="{Binding OrderInfoVm.AllCountries}"
SelectedValuePath="country_code" DisplayMemberPath="country_name"
SelectedValue="{Binding OrderInfoVm.BillingCountry}" />
Basically I want to bind value to country codes and set the selected value to the country code bound to OrderInfoVm.BillingCountry (which implements INotifyPropertyChanged)
Initially when the control loads selected value is empty, but on click BillingCountry is populated. Selected value does not seem to change. How can I remedy that?
I do agree with Alex that using SelectedItem gives the desired behaviour. See the code below. It works and will hopefully help you further:
<Window x:Class="SelectedValueSpike.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ComboBox ItemsSource="{Binding OrderInfoVm.AllCountries}"
SelectedValuePath="country_code" DisplayMemberPath="country_name"
SelectedItem="{Binding OrderInfoVm.BillingCountry}"
IsSynchronizedWithCurrentItem="True"
Name="AllCountriesBox"/>
<TextBox Text="{Binding ElementName=AllCountriesBox, Path=SelectedValue}"/>
<Button>
Change the textbox to "Ca","NL",or "US" and click!
</Button>
</StackPanel>
</Window>
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace SelectedValueSpike
{
public partial class Window1 : Window
{
public OrderInfoVm OrderInfoVm{ get; set;}
public Window1()
{
InitializeComponent();
OrderInfoVm=new OrderInfoVm();
OrderInfoVm.AllCountries.Add(new Country("US","US of A"));
OrderInfoVm.AllCountries.Add(new Country("NL","Netherlands"));
OrderInfoVm.AllCountries.Add(new Country("Ca","Canada"));
OrderInfoVm.BillingCountry = OrderInfoVm.AllCountries[1];
DataContext = this;
}
}
public class OrderInfoVm:INotifyPropertyChanged
{
public OrderInfoVm()
{
AllCountries=new ObservableCollection<Country>();
}
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Country> _allCountries;
public ObservableCollection<Country> AllCountries
{
get { return _allCountries; }
set
{
_allCountries = value;
OnPropertyChanged("AllCountries");
}
}
private Country _billingCountry;
public Country BillingCountry
{
get { return _billingCountry; }
set
{
_billingCountry = value;
OnPropertyChanged("BillingCountry");
}
}
private void OnPropertyChanged(string property)
{
if(PropertyChanged!=null)
PropertyChanged(this,new PropertyChangedEventArgs(property));
}
}
public class Country
{
public string country_code { get; set; }
public string country_name { get; set; }
public Country(string code, string name)
{
country_code = code;
country_name = name;
}
}
}
Maybe you are trying to implement something similar to this: Bound ComboBox
Try changing it to SelectedItem and set Mode=TwoWay...
<ComboBox ItemsSource="{Binding OrderInfoVm.AllCountries}"
SelectedValuePath="country_code" DisplayMemberPath="country_name"
SelectedItem="{Binding OrderInfoVm.BillingCountry, Mode=TwoWay}" />
Edit: You may not need to change it to SelectedItem, perhaps just setting TwoWay will work, but that is how I've done it in my own code.
Please ensure that you've specified correct binding path.
Try starting project in debug mode and look at the output window to see if there are any binding errors
Give this a shot; I believe you have your SelectedValuePath and SelectedValue mixed up:
<ComboBox ItemsSource="{Binding OrderInfoVm.AllCountries}"
SelectedValue="country_code"
DisplayMemberPath="country_name"
SelectedValuePath="{Binding OrderInfoVm.BillingCountry}" />
For Reference:
ItemsSource = Gets or sets a collection used to generate the content of the ItemsControl (ComboBox).
SelectedValue = Gets or sets the value of the SelectedItem, obtained by using SelectedValuePath.
SelectedValuePath = Gets or sets a value that indicates the path used to get the SelectedValue from the SelectedItem.
DisplayMemberPath = Gets or sets a path to a value on the source object to serve as the visual representation of the object.

Resources