I have a WPF control pane with sixteen of the same child control containing a combobox that needs to be bound to a list in the Parent Control code behind. I was really struggling to get this list to bind until I found this: Binding objects defined in code-behind.
Setting DataContext="{Binding RelativeSource={RelativeSource Self}}" on the Parent Control allowed me to bind the combobox on the child control directly.
The problem is that now I want to create a Data Template to display the list items properly, but nothing I put in the Binding or Relative Source Displays anything.
ControlPane.xaml
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlPane"
x:Name="CtrlPaneWpf"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
ControlPane.xaml.cs
public class TwoStringClass
{
public string string1;
public string colorHex;
}
public ObservableCollection<TwoStringClass> TwoStringClass1
{
get
{
ObservableCollection<TwoStringClass> cmbclrs = new ObservableCollection<TwoStringClass>();
cmbclrs.Add(new TwoStringClass() { string1 = "This", colorHex = "#FF0000" });
cmbclrs.Add(new TwoStringClass() { string1 = "That", colorHex = "#FF352E2" });
cmbclrs.Add(new TwoStringClass() { string1 = "The Other", colorHex = "#FFF4F612" });
return cmbclrs;
}
}
ChildControl.xaml
<UserControl
x:Name="ChildControl"
>
<ComboBox x:Name="cmbFontColor" ItemsSource="{Binding TwoStringClass1}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding string1}" />
<Rectangle Fill="{Binding colorHex}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</UserControl>
I know the Binding is Working because I get the correct number of (blank) items in the Combobox and can see the class name if I remove the ItemTemplate.
I can't figure out for the life of me why binding to the property name isn't working here as it does when the list comes from a control's own code behind.
There must be some other information I need to add to the TextBlock binding, but no matter what DataContext or RelativeSource I try, I always get blank items.
Data binding in WPF works with public properties only, not with fields. Your item class should look like this:
public class TwoStringClass
{
public string string1 { get; set; }
public string colorHex { get; set; }
}
That said, there are widely accepted naming convention, according to which you should use Pascal case for property names, e.g. String1 and ColorHex.
I believe the answer to your question is the same as the answer to a question I posted recently, which was answered by StewBob. Here is my slightly modified version of his answer, which should also fix the issue you are having.
You can see my original thread here: WPF ListBox with CheckBox data template - Binding Content property of Checkbox not working
I see you added "This", "That", and "The Other" as your data source, presumably for simplicity in posting this question to SO. Please be aware that if your true underlying data source can change, when doing DataBinding, your class needs to implement INotifyPropertyChanged for the data to properly display in the UI. An example:
public class TwoStringClass: INotifyPropertyChanged
{
private string _String1;
private string _ColorHex;
public string String1
{
get
{
return _String1;
}
set
{
if (value != _String1)
{
_String1 = value;
NotifyPropertyChanged("String1");
}
}
}
public string ColorHex
{
get
{
return _ColorHex;
}
set
{
if (value != _ColorHex)
{
_ColorHex = value;
NotifyPropertyChanged("ColorHex");
}
}
}
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
This shows the class, two properties, and the needed event and method for implementing INotifyPropertyChanged.
Related
I have a TextBlock in a ComboBox in a C# WPF project bound to a list of 'Envelope' items, which have a string 'Name' and a double 'Weight' property, the former of which I would like to see displayed in the TextBlock.
When I run my program, the ComboBox appears without any text in it. It properly has three unlabeled items in it, and if I view the ItemsSource or SelectedItem of the ComboBox they show the appropriate values, and other code which interacts with the SelectedItem of the ComboBox behaves properly. The only thing that does not work is that the TextBlock contains no text. If I replace the "{Binding Name}" with "au ghdfjlnvgmumar" then the appropriate garbled characters appear in the ComboBox, so it is definitely a problem with the binding. What is the problem, and how can I get around it?
Relevant code:
xaml:
<ComboBox Name="EnvelopeList" HorizontalAlignment="Center" Width="200" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
C#:
//main window code
public MainWindow()
{
InitializeComponent();
envelopes = new List<Envelope>();
envelopes.Add(new Envelope("TEST", 0));
envelopes.Add(new Envelope("HI", 10));
EnvelopeList.ItemsSource = envelopes;
}
//Envelope class
class Envelope
{
public string Name;
public double Weight;
public Envelope()
{
Name = "[None]";
Weight = 0;
}
public Envelope(string n, double w)
{
Name = n;
Weight = w;
}
public override string ToString()
{
return Name;
}
}
When DataBinding, you can only bind to Properties. Also, you need to update your properties with a PropertyChangedEvent. Otherwise, if you change your property after the initial binding it won't update the UI.
You need to use on property changed and a property
public class Envelope: ModelBase
{
private string _name;
public string Name
{
get { return _name; }
set { _name= value; OnPropertyChanged("Name"); }
}
}
public class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
}
Finally, I notice that you're setting ItemsSource directly. Instead, you want to set your View's DataContext property and then bind to your ItemsSource
Here is a MSDN article on DataBinding that will teach you how to do it properly.
Name is a field, you can only bind to properties.
I'm learning WPF. I figured out how to bind to a list box using the following code.
XAML
<ListBox Name="lstUpdates" Grid.Row="1" Grid.Column="0" Margin="5,0,5,5" ItemsSource="{Binding HistoryData}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Loaded="lstUpdates_Loaded">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel >
<TextBlock Text="{Binding Header}" FontWeight="Bold" />
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" Height="46" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
CS
public class HistoryRow
{
public string Header { get; set; }
public string Description { get; set; }
public int ArticleUpdateId { get; set; }
}
public IEnumerable<HistoryRow> HistoryData { get; set; }
DataContext = this;
HistoryData = new List<HistoryRow>
{
new HistoryRow { Header = "10/29/1961", Description = "Blah blah" },
new HistoryRow { Header = "12/2/1976", Description = "Blah blah" },
new HistoryRow { Header = "5/24/1992", Description = "Blah blah" },
new HistoryRow { Header = "2/18/2012", Description = "Blah blah" },
};
I've got this so it works pretty well. But now my question is how can I refresh my listbox when my data changes. I've found several "solutions" on the Web and none of them work for me.
You need to inform a view about changes of your collection.
When using ObservableCollection as source of ListBox control, the view listening a event CollectionChanges and update control when event get raised.
So change type of HistoryData to ObservableCollection<YourHistoryDataType>
To work properly you need keep same instance of collection and update only items.
To keep same instance create a property on your viewmodel:
private ObservableCollection<HistoryRow> _HistoryData;
public ObservableCollection<HistoryRow> HistoryData
{
get
{
return _HistoryData;
}
set
{
If(Equals(_HistoryData, value) return;
_HistoryData = value;
this.OnPropertyChange(nameof(this.HistoryData);
}
}
Calling OnPropertyChanged will inform view about that instance of collection was changed.
Read this: Implementing the Model-View-ViewModel Pattern
There are some information about ObservableCollection and PropertyChanged event
Your class HistoryRow will need to extend INotifyOfPropertyChange,
Then instead of using an Ienumerable, it needs to be of type ObservableCollection and subscribe to PropertyChangeEvent
private ObservableCollection<HistoryRow> _historyData;
public ObservableCollection<HistoryRow> HistoryData
{
get {return _historyData}
set {_historyData = value; OnPropertyChange("HistoryData");}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
My solution: Don't use binding. Just populate the list by creating a collection and assigning it to the ItemsSource property. Then, when the data has changed, do it again.
I don't know why WPF has to make so many things a pain, but when it's difficult to get a clear answer on stackoverflow on how to refresh the list, or I'm looking into reading a bunch of articles for such a simple task, it's probably a good indication I'm on the wrong track.
Perhaps I will find a reason to get into MVVM design and binding. But for now, all I want to do is update my ListBox.
I have a wpf c# application.
I am using a combo box and I have set its itemsource property to an observable collection.
The problem I have is that when I modify this collection the changes are not reflected in my drop down.
so i am wondering what I have done wrong?
This is my class object:
public class JobTicker
{
public string CustomerRef { get; set; }
public string JobRef { get; set; }
public int JobId { get; set; }
public string CustomerJobDetails { get; set; }
public string CustomerName { get; set; }
}
I bind to my collection:
ActiveState.JobsActive = new ObservableCollection<JobTicker>('data from a list');
my declaration of the collection variable:
public static ObservableCollection<JobTicker> JobsActive = new ObservableCollection<JobTicker>();
My combo Box (which is on a userControl of mine that is loaded when my app starts)
<xctk:WatermarkComboBox x:Name="cboActiveJobs" Grid.Row="1" Grid.Column="2"
Width="250" Watermark="Select Customer"
DisplayMemberPath="CustomerJobDetails"
HorizontalContentAlignment="Center"
SelectionChanged="cbo_SelectionChanged"
DropDownOpened="cbo_DropDownOpened"
DropDownClosed="cbo_DropDownClosed"
Style="{StaticResource ComboBoxFlatStyle}"
/>
and my code behind:
cboActiveJobs.ItemsSource = ActiveState.JobsActive;
Now if I modify 'ActiveState.JobsActive' I would expect changes to be reflected in my dropdown but they are not.
The code you have isn't actually binding it. It's just assigning a collection to a property.
The combo box's ItemsSource property can't listen for notifications from the ObservableCollection. Instead, you need an instance of the Binding class to listen for those notifications and make the UI updates happen. Binding is where all the magic is. You could create one programmatically in code behind and attach it (see links below), but the easiest and by far most common way is to bind in XAML:
<xctk:WatermarkComboBox
ItemsSource="{Binding JobsActive}"
SelectedItem="{Binding SelectedCustomer}"
x:Name="cboActiveJobs"
Grid.Row="1"
Grid.Column="2"
Width="250"
Watermark="Select Customer"
DisplayMemberPath="CustomerJobDetails"
HorizontalContentAlignment="Center"
SelectionChanged="cbo_SelectionChanged"
DropDownOpened="cbo_DropDownOpened"
DropDownClosed="cbo_DropDownClosed"
Style="{StaticResource ComboBoxFlatStyle}"
/>
Now, JobsActive should be a public property of the view model that the DataContext for that control. If it isn't, that won't work.
Since you've got a SelectionChanged event, I also added a SelectedCustomer binding, which would be a property on your view model as well. The Binding will update this both ways: Change it in your viewmodel, and the combobox selection will change. When the user picks a combobox item, the viewmodel's property value will change.
private JobTicker _selectedCustomer;
public JobTicker SelectedCustomer {
get { return _selectedCustomer; }
set {
_selectedCustomer = value;
// If you're not in C#6, use this instead:
//OnPropertyChanged("SelectedCustomer");
OnPropertyChanged(nameof(SelectedCustomer));
}
}
// Implement INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
If you do want to get this binding working right away without writing a viewmodel, I don't recommend that approach, but it's absolutely doable. There are several answers on StackOverflow that should help with getting that working: WPF Binding Programatically, How to programmatically set data binding using C# xaml.
Question: Can anyone please provide a full code example that shows how one does programmatically change the SelectedItem of a data-bound WPF ComboBox without using MyComboBox.SelectedIndex?
Code sample: Here is what I currently have.
XAML:
<Window x:Class="Wpf.ComboBoxDemo.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.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox Name="MyComboBox" DisplayMemberPath="LastName" SelectedIndex="0"/>
</Grid>
</Window>
Code-behind:
using System.Collections.ObjectModel;
using System.Windows;
namespace Wpf.ComboBoxDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<Person> myPersonList = new ObservableCollection<Person>();
Person personJobs = new Person("Steve", "Jobs");
Person personGates = new Person("Bill", "Gates");
myPersonList.Add(personJobs);
myPersonList.Add(personGates);
MyComboBox.ItemsSource = myPersonList;
// How do I programmatically select the second Person, i.e. "Gates"?
// The best pratice must be to somehow to set something like IsCurrentlySelected on the model, so the view update automatically. But how?
MyComboBox.SelectedIndex = 1; // This works, but is there no way without using the index?
}
private class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
}
}
Similar questions: I have of course searched the Internet first, but found nothing that helped me.
Changing the SelectedItem of a enum-bound combobox inside ViewModel (MSDN)
Programmatically set ComboBox SelectedItem in WPF (3.5sp1) (Stack Overflow)
At the top of my head (I might be wrong), make the window implement INotifyPropertyChanged and add the event:
namespace Wpf.ComboBoxDemo
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
Then add a property for the currently selected item which notifies on changes:
private Person _selected;
public Person MySelected
{
get { return _selected; }
set
{
if (value != _selected)
{
_selected = value;
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs("MySelected"));
}
}
}
}
Now bind the combobox (the binding could be more advanced here using FindAncestor but sometimes to keep things simple I put the datacontext in code behind):
XAML:
<ComboBox
Name="MyComboBox"
DisplayMemberPath="LastName"
SelectedItem="{Binding MySelected}" />
Code behind:
public MainWindow()
{
InitializeComponent();
// ...
// this will cause the "MySelected" binding to target the correct property on this object
MyComboBox.DataContext = this;
}
I think it goes something like that. I cant test it right now but hopefully it will nudge you in the right direction.
Edit: If you want to try the "other way" of binding heres how. Expand the SelectedItem binding to look like this:
<ComboBox
Name="MyComboBox"
DisplayMemberPath="LastName"
SelectedItem="{Binding MySelected,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}" />
You can now skip setting the DataContext in code behind:
public MainWindow()
{
InitializeComponent();
// ...
// this will cause the "MySelected" binding to target the correct property on this object
//MyComboBox.DataContext = this;
}
This is because that FindAncestor mode makes the ComboBox itself find the object to which property it should bind, rather than you specifically stating.
The current hot topic here at the office is which of these two ways are the best. To me its just more XAML and less code behind (or the other way around), just use the method that places the code where youre comfortable to work. I think there are some scenarios where the latter is preferred (like when you include data binding controls inside other controls), but Im just dabbling so I havent really figured those parts out yet.
In WPF I have a collection of bool? values and I want to bind each of these to a separate checkbox programmatically. I want the bindings to be TwoWay so that changing the value of the individual item in the collection in code updates the check box and vice versa.
I have spent ages trying to figure out how to do this and I am completely stuck. With the following code the checkbox only gets the right value when the window is loaded and that's it. Changing the check box doesn't even update the value in the collection. (UPDATE: this appears to be a bug in .NET4 as the collection does get updated in an identical .NET3.5 project. UPDATE: Microsoft have confirmed the bug and that it will be fixed in the .NET4 release.)
Many thanks in advance for your help!
C#:
namespace MyNamespace
{
public partial class MyWindow : Window, INotifyPropertyChanged
{
public MyWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public List<bool?> myCollection = new List<bool?>
{ true, false, true, false, true, false };
public List<bool?> MyCollection
{
get { return myCollection; }
set { myCollection = value; }
}
}
}
XAML:
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}">
There are a few things that need changing here to get this to work. Firstly you'll need to wrap your boolean value in an object that implements the INotifyPropertyChanged interface in order to get the change notification that you are looking for. Currently you are binding to boolean values in your collection which do not implement the interface. To do this you could create a wrapper class like so :
public class Wrapper: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private bool val = false;
public bool Val
{
get { return val; }
set
{
val = value;
this.OnPropertyChanged("Val");
}
}
public Wrapper(bool val)
{
this.val = val;
}
}
You'll then want to create these objects in your form instead of a list of booleans. You may also want to use an observable collection instead of a list so that notification of items being added and removed are sent. This is shown below:
public Window1()
{
InitializeComponent();
this.DataContext = this;
}
private ObservableCollection<Wrapper> myCollection = new ObservableCollection<Wrapper>()
{new Wrapper(true), new Wrapper(false), new Wrapper(true)};
public ObservableCollection<Wrapper> MyCollection
{
get { return myCollection; }
}
The next thing to do is to display a list of check boxes in your ui. To do this WPF provides itemscontrols. ListBox is an itemscontrol so we can use this as a starting point. Set the itemssource of a listbox to be MyCollection. We then need to define how each Wrapper object is going to be displayed in the list box and this can be done with a datatemplate which is created in the windows resources. This is shown below :
<Window.Resources>
<DataTemplate x:Key="myCollectionItems">
<CheckBox IsChecked="{Binding Path=Val, Mode=TwoWay}"></CheckBox>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=MyCollection}" ItemTemplate="{StaticResource myCollectionItems}"></ListBox>
</Grid>
This should get you up and running with a simple demo of checkboxes that have values bound to a list of booleans.
What makes you think it's not working? It's working for me :)
Here's my test XAML:
<UniformGrid>
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}"/>
<ListBox ItemsSource="{Binding MyCollection}"/>
<Button Content="Test" Click="Button_Click"/>
</UniformGrid>
Here's my code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
}
(the rest is the same as yours)
I placed a breakpoint on Button_Click and checked MyCollection[0] it was updated according to the IsChecked value of the CheckBox.
Try changing your collection type from List<bool?> to ObservableCollection<bool?> perhaps that is the reason you think it's not working for you (the fact that changes to the collection are not reflected anywhere else in your view).
Change your List<bool?> to an ObservableCollection<bool?>. A List does not raise the change notifications that WPF needs to update the UI. An ObservableCollection does. This handles the case where the list entry is changed and the CheckBox needs to update accordingly.
In the other direction, it works for me even with a List<bool?> -- i.e. toggling the checkbox modifies the value in the collection. Your binding syntax is certainly correct.