Assume the following:
Public ObservableCollection<string> SomeCollection;
Public ObservableCollection<string> SomeOtherCollection
{
get{ return SomeCollection; }
set{ SomeCollection = value; }
}
Both are bound to a ListBox in a WPF UI using
<ListBox ItemsSource="{Binding SomeCollection}" />
<ListBox ItemsSource="{Binding SomeOtherCollection} />
Assuming this, will both update automatically? To my understanding, the first will, but I know absolutely nothing as to the functionality of the second.
Note: This is a rough example. Please don't be hyperliteral with me. :)
The first example will not be able to bind to the UI (only properties may be bound to the UI in wpf), however if you changed it to:
Public ObservableCollection<string> SomeCollection { get; set; }
They would be equivalent. The get; set; is just shorthand for your second example. If you ever want to add any extra logic in either the getter or the setter (like implementing INotifyPropertyChanged), you would have to write out the whole thing. If you just want the simple get and set, the above is the same thing.
Related
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;
I have a list of objects (ObservableCollection subjectlist) and want to display them in a Combobox via data-binding and dependency property.
WPF Data Binding to a Combo Box
I searched on stackoverflow and tried to implement the solution of Craig Suchanec in the link above. (tried the whole day now and I just don't get what's wrong with my code)
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public static readonly DependencyProperty SubjectListProperty =
DependencyProperty.Register("SubjectList",
typeof(ObservableCollection<Subject>),
typeof(MainWindow));
private ObservableCollection<Subject> subjectList = new ObservableCollection<Subject>();
Initialization init1;
public ObservableCollection<Subject> SubjectList
{
get { return (ObservableCollection<Subject>)GetValue(SubjectListProperty); }
// get { return subjectList; }
}
public MainWindow()
{
init1 = new Initialization();
subjectList = init1.createMenuSubject();
InitializeComponent();
//this.comboBox.DataContext = SubjectList;
}
}
MainWindow.xaml
<Grid>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left"VerticalAlignment="Top" Width="120" Margin="321,10,0,0"
ItemsSource="{Binding ElementName=mainWindow, Path=SubjectList}" DisplayMemberPath="Name"/>
</Grid>
It DOES work if I just set the DataContext and work without dependency property, but as soon as I try to use the dependency property for data-binding it does NOT and I don't see the significant difference between my implementation and the solution given in the link.
It would be much appreciated, if somebody could help me with this problem.
I can't see anywhere in your code where you are actually setting the value of the SubjectList property.
You are however setting the value of subjectList, but you're binding to SubjectList. Note the casing difference.
You should write:
public ObservableCollection<Subject> SubjectList
{
set { base.SetValue(SubjectListProperty, value); }
get { return (ObservableCollection<Subject>)base.GetValue(SubjectListProperty); }
}
instead of
public ObservableCollection<Subject> SubjectList
{
set { base.SetValue(SubjectListProperty, value); }
get { return subjectList; }
}
or any other ad hoc format. You are setting subjectList in your constructor MainWindow(), however, it will not set the value of SubjectList (with Capital S) and a property change event is never raised. Remove subjectList.
If you are wondering why the DataContext approach works, you should note it will work even if you do not use a DepenedencyProperty. However, if you implement INotifyPropertyChange, it will work with setting ElementName too.
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 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!).
I'm puzzled by this probably trivial matter. I have my custom property on a descendant of DevExpresses class LayoutGroup (shouldn't make any difference IMO):
public class ExpandableLayoutGroup : LayoutGroup
{
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register("IsExpanded", typeof(Boolean), typeof(ExpandableLayoutGroup));
public Boolean IsExpanded
{
get { return (Boolean) GetValue(IsExpandedProperty); }
set
{
expandCheckBox.IsChecked = value;
SetValue(IsExpandedProperty, value);
}
}
}
Then I have XAML binding to a listbox (containing 2 items at this time) called listStates.
<ExpandableLayoutGroup x:Name="groupTool" IsExpanded="{Binding SelectedValue.IsTool, ElementName=listStates}" DataContext="{Binding Tool}" Header="Tool" View="GroupBox" />
The object list binding to listStates contains both properties (simplified):
public class State
{
public Boolean IsItemTool { get; private set; }
public Tool Tool { get; private set; }
}
Am I missing something obvious? Because I would expect that when I change listbox selection (single) it would also update IsExpanded property on a group. I have more subproperties of Tool binded (as apparent by DataContext="{Binding Tool}") and those update well. So the DataContext is changing correctly on listbox selection. Except this one property IsExpanded (that should expand/collapse layoutgroup).
What am I doing wrong, and how to make it so, that when listbox changes, IsExpanded binding is polled to get value from IsTool, and will set it (depending on selection).
Getters and setters of dependency properties must contain only GetValue and SetValue calls, because there're two ways to get and set their values: using getters and setters and using DependencyProperty or DependencyPropertyKey. This is why code in your setter is not executed (bindings don't use them for dependency properties). If you want some code to be executed when the value is changed, specify it as a PropertyChangedCallback callback in PropertyMetadata.
See PropertyChangedCallback Delegate on MSDN.
The setter for a dependency property must only set the value for it's underlying type. The .NET internals reserve the right to call SetValue() directly (and in my experience WPF usually does, while Silverlight uses the setter that you've defined).
Refer to the "Implications for Custom Dependency Properties" section of XAML Loading and Dependency Properties for details