I have a Listbox with a DataTemplate which includes a Combobox. I need to change the selectedItem/Index of a particular ComboBox. How would I access it?
Additional Detail
All the combobox have the same options. If a Combobox is set to the same value as another ComboBox then the Combobox that was set first should return to empty (which is the first item in my cbxOptions Dictionary that the ComboBoxes are Bound to).
<DataTemplate x:Key="lbxHeaderDataTemplate">
<Grid>
<Label Content="{Binding Item1}"></Label>
<ComboBox Grid.Column="1" ItemsSource="{Binding Item2}"
DisplayMemberPath="Key" SelectionChanged="ComboBox_SelectionChanged"></ComboBox>
</Grid>
</DataTemplate>
C#
Populate UI
foreach (DataColumn dc in _loadedData.Columns)
{
ListBox.Items.Add(new Tuple<string, Dictionary<string, string>>
(dc.ColumnName, cbxOptions));
}
Trying to wipe combobox
This is where I would expect I could foreach through the Listbox, checking the controls for a match at which point I'd change it to blank. However my foreach just gives me back stupid Tuple...which is readonly but I don't think that'd update my ComboBox anyways.
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox cbxSelected = (ComboBox)sender;
DependencyObject parent = VisualTreeHelper.GetParent(cbxSelected);
Label currentLbl = null;
foreach (object o in ((Grid)parent).Children)
{
if (o is Label)
{
currentLbl = (Label)o;
}
}
string LblText = currentLbl.Content.ToString();
string cbxValue = cbxSelected.SelectedValue.ToString();
//HERE I want to iterate through the listbox controls, not the datasource
foreach (Tuple<string, Dictionary<string, string>> l in lbxDatFields.Items)
{
//l.Item2 = "";
if (l.Item1.EndsWith(cbxOptions[cbxValue]))
l = new Tuple<string, Dictionary<string, string>>(l.Item1, "");
}
}
I'm sure there must be a very simple way of accessing the control. Any help would be much appreciated. Please let me know if additional info is required.
Without a good Minimal, Complete, and Verifiable code example that clearly and concisely illustrates your question, it's not practical to try to address your current design. Based on the bit of code you did post, one can make some observations though:
Since Tuple<...> is immutable, you can't modify the Item2 property. You have to replace the entire Tuple<...> object with a new one.
The code you posted shouldn't even compile, because you are trying to modify the l variable in the foreach loop.
Even if you could, it wouldn't change the element in the list itself, just that particular variable.
Not that you even want to change the element; it's the selection of the combo box that should change, not its Item2 options.
The use of a dictionary object for the ComboBox items eludes me. Perhaps with a complete code example, it would be more clear.
All that said…
How would I access it?
This question comes up only because you are misusing WPF to start with. You should not be manipulating the UI directly; instead, your UI state should be represented in view model data structures. Then the ComboBox selection would be bound to a view model property, and the answer to your question would be simply to look at that property.
It's hard to know for sure, given the lack of details, but it appears to me that you are trying to implement a scenario where you have a list of items, where each item has a selectable option, and you want those options to be mutually exclusive. That is, only one item at a time can have any given option.
Assuming that's the case, I will show an implementation that in my opinion is much better than the approach you are attempting to implement. That is, it uses the basic idea I've proposed above, where you start with the data models, and then work back to the UI from there. Doing it this way, the data models are very simple and easy to understand, and so is all of the implementation for the behavior you want.
It looks like this…
First, start with the basic per-item view model data structure:
class PropertyChangedExEventArgs<T> : PropertyChangedEventArgs
{
public T OldValue { get; }
public PropertyChangedExEventArgs(string propertyName, T oldValue)
: base(propertyName)
{
OldValue = oldValue;
}
}
class ItemViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set { _UpdateField(ref _name, value); }
}
private string _value;
public string Value
{
get { return _value; }
set { _UpdateField(ref _value, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this,
new PropertyChangedExEventArgs<T>(propertyName, oldValue));
}
}
Notes:
The above class implements INotifyPropertyChanged directly. In a real-world program, this implementation would typically be in a base class, which each view model class inherits. If you do a significant amount of WPF programming, you'll have this base class as a reusable component you just include in each project, either in a separate project you reference, or as a code snippet. There are many WPF user frameworks you can use as well, which provide this functionality.
In this particular example, there's not already a convenient mechanism for event subscribers to know the old value of the property after it's changed, but the logic involved requires that, so that the key for the mapping from value to model object can be removed from the dictionary when it's no longer valid. There are a variety of ways to address that need — arguably, the more straightforward is to just do a linear search of the Values collection of the relatively small dictionary. But I decided to extend the PropertyChangedEventArgs class instead, as that's a more scalable solution to that particular need (and so is more useful as a general solution to the problem).
Here, I only need one class to implement that interface, and it's simpler for the sake of illustration to keep everything together there.
Okay, so with the per-item data structure in place, we also want a parent data structure to encapsulate these items as a collection and to handle the broader manipulation of these items:
class MainViewModel
{
public ObservableCollection<ItemViewModel> Items { get; } =
new ObservableCollection<ItemViewModel>
{
new ItemViewModel { Name = "Item #1" },
new ItemViewModel { Name = "Item #2" },
new ItemViewModel { Name = "Item #3" },
};
public IReadOnlyList<string> Options { get; } =
new [] { "Option One", "Option Two", "Option Three" };
private readonly Dictionary<string, ItemViewModel> _valueToModel =
new Dictionary<string, ItemViewModel>();
public MainViewModel()
{
foreach (ItemViewModel itemModel in Items)
{
itemModel.PropertyChanged += _ItemPropertyChanged;
}
}
private void _ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ItemViewModel.Value))
{
ItemViewModel itemModel = (ItemViewModel)sender;
PropertyChangedExEventArgs<string> exArgs =
(PropertyChangedExEventArgs<string>)e;
if (exArgs.OldValue != null)
{
_valueToModel.Remove(exArgs.OldValue);
}
if (itemModel.Value != null)
{
if (_valueToModel.TryGetValue(
itemModel.Value, out ItemViewModel otherModel))
{
otherModel.Value = null;
}
_valueToModel[itemModel.Value] = itemModel;
}
}
}
}
This object maintains the collection of items, as well as the collection of options for the ComboBox elements. This is also where the logic to handle the mutual-exclusion of options is handled, because this is the class that has access to all of the per-item data objects.
On that last point: you could, of course, provide a way for the per-item objects to interact with the parent data structure to be able to enumerate the other per-item objects. This would allow each per-item object to handle its own property changes, so that the parent object doesn't need to subscribe to each per-item object's PropertyChanged event. But doing so would also increase coupling between the classes and make the basic logic harder to follow. IMHO, it is preferable to keep this top-down approach, where owned objects know as little as possible about their owners (and in this case, nothing at all).
Note that with the above, all of the logic necessary to track the state of the items and ensure mutual exclusion of the options setting is present, without anything that is actually specific to the view objects. The above code would work in any program, with or without a user interface. It's completely decoupled from the view itself.
And so, how does the view use it? Like this:
<Window x:Class="TestSO45196940ComboBoxExclusive.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:l="clr-namespace:TestSO45196940ComboBoxExclusive"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<StackPanel>
<ListBox ItemsSource="{Binding Items}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type l:ItemViewModel}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Name}"/>
<ComboBox ItemsSource="{Binding DataContext.Options, RelativeSource={RelativeSource AncestorType=ListBox}}"
SelectedItem="{Binding Value}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>
</StackPanel>
</Window>
Similar to how the ItemViewModel object knows nothing about the MainViewModel, but rather the latter subscribes to the former's PropertyChanged event and accesses the item object's properties to do the work, the view binds to the relevant properties of both model objects, without those objects having any need to know about those bindings, or the view itself.
The view has no code-behind at all. It's just a simple, declarative description of what the user sees, and does nothing more than present to the user the current state of the underlying data.
Doing it this way keeps everything very simple and disconnected, so that each object has a very narrow set of responsibilities, and the interaction between objects is kept to a minimum. This makes it easier to assure that the code is correct, and reduces the mental workload when implementing features, because you're only dealing with a small section of the code at a time, instead of having to keep straight how everything relates to each other.
For what it's worth, it took way longer to explain the code above here in this post, than it did to write the code itself. Following the standard WPF idioms, the actual authoring the code can go very quickly, especially if you already have the basic base classes in place for things like INotifyPropertyChanged. Much of that time savings comes from not having to puzzle over how to get at the data you need. By following better practices, the data is always already right there where you want it.
I have a Listbox with a DataTemplate which includes a Combobox. I need to change the selectedItem/Index of a particular ComboBox. How would I access it?
By accessing the corresponding data item in the Items collection of the ListBox.
Replace your Tuple<string, Dictionary<string, string>> with a class that also includes a SelectedIndex property. Make sure that you implement the INotifyPropertyChanged interface correctly:
class DataItem : INotifyPropertyChanged
{
public string Item1 { get; set; }
public Dictionary<string, string> Item2 { get; set; }
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
...
foreach (DataColumn dc in _loadedData.Columns)
{
ListBox.Items.Add(new DataItem() { Item1 = dc.ColumnName, Item2 = cbxOptions });
}
Then you bind the SelectedIndex property of the ComboBox in your DataTemplate to your SelectedIndex property:
<ComboBox Grid.Column="1" ItemsSource="{Binding Item2}" DisplayMemberPath="Key"
SelectedIndex="{Binding SelectedIndex}"></ComboBox>
And change the selected index of a ComboBox by setting the source property of the corresponding object in the Items collection:
(ListBox.Items[2] as DataItem).SelectedIndex = 1;
Related
I need to make list of items. I binded collection of users to listbox. Everything works quite well, but items in listbox aren't updated in real time. They aren't updated at all by this binding. So when I remove any user from the list, listbox isn't updated even if its source is correctly changed.
Source is located in data view model at path DataViewModel.Instance.AllUsers; Whenever I add to this list new item or remove one, layout does not update. Other bindings work well. I tried to update listbox layout, to raise event of source update, other way of adding/removing items, but nothing worked.
I tried to debug binding, but I have too many bindings to find the error.
Thanks in advance for any useful advice.
Listbox:
<ListBox x:Name="ListboxUsers" ItemsSource="{Binding Path=AllUsers, Mode=OneWay}" Grid.Column="1" Margin="0" Grid.Row="5" Background="DimGray" BorderThickness="0" Visibility="Hidden" SelectionChanged="ListboxUsers_SelectionChanged"/>
Code-behind:
CatalogueGrid.DataContext = DataViewModel.Instance; //whole view model added as datacontext
DataViewModel class:
public class DataViewModel : INotifyPropertyChanged
{
private static DataViewModel _dataViewModel;
private Collection<UserModel> allUsers;
public Collection<UserModel> AllUsers
{
get
{
return allUsers;
}
set
{
allUsers = value;
NotifyPropertyChanged("AllUsers");
}
}
private DataViewModel()
{
AllUsers = new Collection<UserModel>();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
.
.
.
}
use ObservableColLection instead if Collection wich implements the INotifyCollectionChanged Interface :
private ObservableCollection<UserModel> allUsers;
public ObservableCollection<UserModel> AllUsers
{
get
{
return allUsers;
}
set
{
allUsers = value;
NotifyPropertyChanged("AllUsers");
}
}
For changes to the collection to propagate to the UI, the collection class needs to implement INotifyCollectionChanged.
A very useful class that already implements this is ObservableCollection<T> (MSDN). Use that instead of Collection<T>.
You have bound your listbox to a Collection<T> - that is just a list, which does not issue any notifications to bound properties that its contents has changed. Hence, your listbox cannot possibly know when the collection has changed.
Instead, you can use the ObservableCollection<T> class (or, more precisely, any collection that also implements INotifyCollectionChanged), and changes will be automatically propagated to the listbox.
Note that your property does not have to be typed as ObservableCollection<T>, you can also just declare your public property as IEnumerable<T> or IList<T>; the binding will find out on its own whether the returned class also implements INotifyCollectionChanged. Like this, you are free to replace your actual underlying collection class later on, for example with a ReadOnlyObservableCollection<T>, in case you want to disallow changes from the outside.
Speaking of this, a note on your code: You have provided your AllUsers property with a setter. This may lead to undesired consequences, as you open up possibilities for some other code to set the property to null, which (depending on the rest of your code) might lead to exceptions. Unless you actually want to allow assigning new values, for the ItemsSource property binding, a read-only property is fully sufficient, as long as the returned collection object implements INotifyCollectionChanged.
First, I want to say that after several study on 2 different threads (shown below), I decided to post this question since it's quite different.
So, I want to bind an ItemsControl from my view to a property to get a reversed version of a collection.
I have this view (trimmed for clarity) :
<UserControl x:Class="NumberedMusicalScoresWriter.V.NotationGroupV" ...>
...
<Grid>
<ItemsControl ...
ItemsSource="{Binding ReversedNotationVMs, Mode=OneWay}">
...
</ItemsControl>
</Grid>
...
</UserControl>
And, I have this viewmodel (trimmed for clarity) :
public class NotationGroupVM : ...
{
...
public ObservableCollection<NotationVM> ReversedNotationVMs
{
get { return (ObservableCollection<NotationVM>)NotationVMs.Reverse(); //ERROR!! }
}
public ObservableCollection<NotationVM> NotationVMs
{
get { return _notationVMs; }
set { _notationVMs = value; NotifyPropertyChanged("NotationVMs"); NotifyPropertyChanged("ReversedNotationVMs"); }
}
}
But there's this error (See error comment above to spot the problematic line) :
Unable to cast object of type
'd__a01[NumberedMusicalScoresWriter.VM.NotationVM]'
to type
'System.Collections.ObjectModel.ObservableCollection1[NumberedMusicalScoresWriter.VM.NotationVM]'.
I've also tried to apply .ToList<NotationVM>() before reversing, and making a new collection each time the main field got updated. But they didn't work out.
I also need to keep the reversed to be sync-ed with the un-reversed one. NOT just one time reversion only
I've also read an issue about it here and here, but all of them provide either the xaml solution only or i didn't understand them. I need the VM one.
Thanks.
I agree with the comments above that a different approach may give you a better result, but to answer the question as asked:
NotationVMs.Reverse() returns an IEnumerable. You can't cast this directly to an ObservableCollection because, even though ObservableCollection is one implementation of IEnumerable, it happens to not be the implementation that this particular function is returning. You can always cast an ObservableCollection to an IEnumerable, but the opposite is not always true (all squares are rectangles, but not all rectangles are squares).
To return a reversed collection, try this:
public ObservableCollection<NotationVM> ReversedNotationVMs
{
get { return new ObservableCollection<NotationVM>(NotationVMs.Reverse()); }
}
In order to keep this in sync with the NotationVMs collection, you'll need to watch for collection changed events:
public ObservableCollection<NotationVM> NotationVMs
{
get { return _notationVMs; }
set
{
if (_notationVMs != null)
{
_notationVMs.CollectionChanged -= OnNotationVMsCollectionChanged;
}
_notationVMs = value;
if (_notationVMs != null)
{
_notationVMs.CollectionChanged += OnNotationVMsCollectionChanged;
}
NotifyPropertyChanged("NotationVMs");
NotifyPropertyChanged("ReversedNotationVMs");
}
}
private void OnNotationVMsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("ReversedNotationVMs");
}
This will sync changes in your NotationVMs to your ReversedNotationVMs, but not the other way around. Since your binding to ReversedNotationVMs is one way, this should suffice.
just from my mind and maybe not the full answer for you. lets say we have a collection ordered by ID
public OberservableCollection<MyNotation> MySource {get;set;}
then i can create a default view and a Reverse view
public ICollectionView MyViewOrderedByID {get;set;}
public ICollectionView MyViewOrderedByIDReversed {get;set;}
//ctor
this.MyViewOrderedByID = CollectionViewSource.GetDefaultView(this.MySource);//default
this.MyViewOrderedByID.SortDescriptions.Add(new SortDescription("ID", ListSortDirection.Ascending));
this.MyViewOrderedByIDReversed= new CollectionViewSource{ Source=this.MySource}.View;//new one
this.MyViewOrderedByIDReversed.SortDescriptions.Add(new SortDescription("ID", ListSortDirection.Descending));
xaml
<ItemsControl ItemsSource="{Binding MyViewOrderedByID}"/>
<ItemsControl ItemsSource="{Binding MyViewOrderedByIDReversed}"/>
then views changes whenever the Source is changing
i'm looking for a possibility to Update a TextBox TextProperty while typing in an other Textbox.
I got a Couple of Double Properties, all entered via Textboxes and i got 1 ReadOnly Property using the Double Properties for doing some Math and returning the result.
I want that result to be displayed in a ReadOnly TextBox (WPF) and it should update automatically when typing/changing an Input Parameter TextBox.
As i worked with JavaFX the last years i could use a deepbinding to do that:
DoubleBinding calcBind = new DoubleBinding(){
{
super.bind(PropA, PropB, PropC);
}
#Override
protected double computeValue(){
return (PropA * PropB / PropC)
}
};
The Dependencies statet with super.bind(...) are automatically checked if they changed and the result is automatically updated.
Now i'm just lookin for a possibility to realize that in C#
As said i am using a couple of properties. Also INotifyPropertyChanged is implemented. So PropA, B and C stating OnPropertyChanged("PropA") when set. The Calculation is done in a Readonly Property like
public Double MathProp
{
get
{
return PropA*PropB/PropC;
}
}
I am Binding it via Code to a TextBox TextProperty.
Binding mathBinding = new Binding
{
Source = ObjectHolding.MathProp,
Path = new PropertyPath("MathProp"),
UpdateSourceTrigger = UpdateSourceTrigger.Explicit,
Mode = BindingMode.OnyWay
};
TextBox txtMathBox = new TextBox();
txtMathBox.SetBinding(TextBox.TextProperty, mathBinding);
I used the Explicit Trigger to do it the "BruteForce"-Way and updating it manually when a KeyEvent occurs. But its just not working very well and i dont like that way of resolving the problmen. There has to be a more elegant way. I hope u could point me in the right direction.
I thought about using a converter instead of a ReadOnly Property to do the calculations. But i am not converting i am just doing some math and not changing the types.
Would be glad if u got some hints for me.
Thanks
Ok, let me split this into parts:
As i worked with JavaFX the last years
Forget java.
java's approach is tremendously inferior (just as everything else in java), due to the tight coupling that occurs between your UI and the application logic behind it.
If you're working with WPF, it is strongly recommended that you leave behind any and all approaches you might have learned from archaic, legacy technologies and understand and embrace The WPF Mentality (this is getting very repetitive).
Basically, in WPF you don't put "calculations" or other code that doesn't really belong into the UI into UI-related classes. Instead, you create a proper ViewModel which is completely agnostic from the UI and contains whatever data you need to display, and the logic you need to perform on it, and then simply use DataBinding to populate the XAML-defined UI with such data.
So, basically, this is the correct way to do what you're trying to achieve, as per your comments, in WPF:
public class ViewModel : INotifyPropertyChanged
{
private double _value1;
private double _value2;
private double _result;
public double Value1
{
get { return _value1; }
set
{
_value1 = value;
OnPropertyChanged();
OnPropertyChanged("Result");
}
}
public double Value2
{
get { return _value2; }
set
{
_value2 = value;
OnPropertyChanged();
OnPropertyChanged("Result");
}
}
public double Result
{
get { return Value1*Value2; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
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>
<TextBox Text="{Binding Value1, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Value2, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Result, Mode=OneWay}" IsReadOnly="True"/>
</StackPanel>
</Window>
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
Notice how I'm setting UpdateSourceTrigger=PropertyChanged on the first and second bindings. This causes the binding to update the value source immediately when the target changes (which means it's being done at every key press).
Also notice that the ViewModel must Implement INotifyPropertyChanged in order to properly support two-way WPF DataBinding.
Notice how the logic is completely decoupled from the UI and it's really Simple, Simple Properties and INotifyPropertyChanged. No need for bizarre manipulations of the binding framework or stupid #override (whatever that means) boilerplate.
Also see how I'm using XAML rather than procedural code to Define the UI, and then simple DataBinding to connect it to the underlying data. In WPF, manipulating UI elements in procedural code is discouraged and unneeded.
WPF Rocks. C# Rocks. java is legacy.
Let me know if you need further help.
I just started a new wpf project in hopes that I could learn a new technique as opposed to using winForms all the time.
I seem to be having way too much difficulty binding the selected value of a comboBox to an integer variable in my "MainWindow" class.
I have been looking at a host of "simple" examples from sites like codeproject, but they all seem way too complicated to just return the selected value of a comboBox. I am used to setting the "SelectedValueChanged" property and just setting a variable, which takes just a few clicks, like so:
public int value;
public void comboBox_SelectedValueChanged()
{
value = comboBox.SelectedValue();
}
Is there a similarly sweet, simple, and short way to properly "bind" the selected comboBox item to an integer?
I am trying to understand how to use INotifyPropertyChanged but I keep getting errors when I try to use it. This is what I have so far, but to be honest, I'm not sure where I am going with it:
// Combo Box Value
public class ComboValue
{
#region Members
int valueToReturn;
#endregion
# region Properties
public int numWeeks
{
get { return valueToReturn; }
}
#endregion
}
// Veiw Model Class
public class ComboValueViewModel:INotifyPropertyChanged
{
#region Construction
public ComboValueViewModel()
{
}
#endregion
}
and I've never used "#region" before, I have no clue what that is.
Could someone fill me in if I'm headed down the right path here?
You don't mention how much you know of MVVM but here goes. Your view will have an associated ViewModel class. In here you'll expose a property containing the items to bind to the combobox, e.g.:
public List<ComboValue> ComboItems { get; set; }
If you populate this property in the VM's constructor, then a List<> is probably sufficient; however you'll often see an ObservableCollection<> used for this kind of thing - this comes into its own if you need to add or remove items within your VM code - your view will react to such changes and update the list accordingly. This won't happen with a List<>.
As for INotifyPropertyChanged, I haven't implemented this pattern in the above code snippet. Again, it's not strictly necessary if you populate the collection in the VM constructor and won't be re-assigning that property again. However it's good practice to use the INPC pattern on your VM properties. Without it, if you were to reassign that property elsewhere in your code, e.g.:-
ComboItems = aNewListOfItems;
then the view wouldn't be made aware of the property change, and the ComboBox wouldn't update. If you need this to happen then implement the INPC pattern on the property, e.g.:-
public List<ComboValue> ComboItems // or ObservableCollection<ComboValue>
{
get
{
return _comboItems;
}
set
{
if (_comboItems != value)
{
_comboItems = value;
OnPropertyChanged("ComboItems");
}
}
}
As you are working with a ComboBox, your VM should also expose a property that you bind to the control's SelectedItem property. This property should implement INPC, e.g.:-
public ComboValue SelectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
As you select items in the combo, the VM's SelectedItem property will change to reflect the current selection.
Finally, your XAML should end up looking something like this:-
<ComboBox ItemsSource="{Binding ComboItems}" SelectedItem="{Binding SelectedItem}" />
Hope this gives you a little "primer" into WPF binding! (Code snippets taken from memory so may not be 100% correct!).
Edit
Your ComboValue class exposes a numWeeks property. As it stands, you'll probably find that your ComboBox displays a list of ComboValue type names. To get the number to appear, the easiest thing is just to override .ToString() in your class and return the value of numWeeks. For more advanced formatting of items in controls such as this, you'll typically specify an ItemTemplate (again, plenty of examples can be found via Google!).
The setup
So I have a class, ComputerItem, designed to store everything I need to know about a specific computer; these items are stored in an ObservableCollection<ComputerItem>. I then have a custom control ComputerControl, which has (among other things) a few text boxes bound to members of ComputerItem, the bindings made available like so:
<TextBlock Name="tb_computerName"TextWrapping="Wrap" Text="{Binding ElementName=ComputerControl1, Path=computerName}"/>
and in the code behind
public static DependencyProperty computerNameProperty = DependencyProperty.Register("computerName", typeof(string), typeof(ComputerControl), null);
I then create a MultiselectList of ComputerControl objects:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<toolkit:MultiselectList x:Name="lb_computers" IsSelectionEnabledChanged="lb_computers_IsSelectionEnabledChanged"> <!--SelectionChanged="lb_computers_SelectionChanged" >-->
<toolkit:MultiselectList.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="sp">
<local:ComputerControl computerName="{Binding Name}" MACAddress="{Binding DisplayMAC}" playClicked="playClicked_Handler" editClicked="editClicked_Handler"/>
</StackPanel>
</DataTemplate>
</toolkit:MultiselectList.ItemTemplate>
</toolkit:MultiselectList>
</Grid>
and you can see the data bindings in the ComputerControl definition. In the code behind I bind the ObservableCollection to the MultiselectList:
this.lb_computers.ItemsSource = ComputerListMaintainer.GetList();
and all of this (as well as a few things I'm sure I've forgotten to include here) works beautifully to fill the MultiselectList with ComputerControls representing the ComputerItems in the ObservableCollection.
The problem
My issue is that when the underlying ComputerItem changes, the TextBlocks in the corresponding ComputerControl don't update. I've implemented INotifyPropertyChanged in the ComputerItem class:
public class ComputerItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
protected virtual void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public string Name
{
get { return name; }
set { OnPropertyChanged("Name"); name = value; }
}
}
but that didn't solve the problem. I suspect it's something to do with ComputerControl but I have no idea where to start looking; the closest question I've found suggested INotifyPropertyChanged should have been the solution but they weren't using a custom control in that case, just a custom class, if I remember correctly.
What am I missing?
Well your setter is incorrect for starters; also do look into MvvmLight, as it provides a great API for this kind of plumbing work.
public string Name
{
get { return name; }
set
{
if(value != name)
{
name = value;
OnPropertyChanged("Name");
}
}
}
Update:
You shouldn't be setting lb_computers.ItemsSource in your code behind, because that's one time operation and not a binding. It is better to bind to an ObservableCollection of observable objects (aka INotifyPropertyChanged).
Also I'm not sure if you're properly declaring your dependency property, so below you can find a proper setup on how to define a 'bindable' property.
And also with XAML, the architecture of your code matters, to have a sane experience. I highly recommend that you utilize the Mvvm pattern. I find MvvmLight and MEFedMVVM to be great aids in my development. This require a bit of work at the beginning, but it'll be far easier to debug future issues and maintain your code.
If these tips don't help, then I'd have to see your full code.
Declaring a Bindable Property
#region ReportName
public string ReportName
{
get { return (string)GetValue(ReportNameProperty); }
set { SetValue(ReportNameProperty, value); }
}
public static readonly DependencyProperty ReportNameProperty = DependencyProperty.Register("ReportName",
typeof(string), typeof(ExportableGridView), new PropertyMetadata("Report", new PropertyChangedCallback(OnReportNameChanged)));
public static void OnReportNameChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ExportableGridView control = sender as ExportableGridView;
control.titleTextBlock.Text = e.NewValue as string;
}
#endregion ReportName