I have a ListBox and a sample ObservableCollection, when I set listBox1.ItemsSource = _collection; in code-behind file, it works fine, but when I do like this in XAML:
ItemsSource="{Binding Collection}"
It doesn't work. In what could be the problem?
In code-behind I have
public ObservableCollection<FeedItem> Collection
{
get { return _collection; }
}
Mostly likely the DataContext of the ListBox (or any of its parents) is not set.
Bind the DataContext to the object holding the collection, and you should be good to go.
Related
I have a Datagrid with a list binded to ItemsSource and the SelectedItem is binded a single object of this list. My ViewModel implements INotifyPropertyChanged.
The binding works fine, except when there's a variable (canSelectOtherObject = false) that prevents myObject of changing it's value. Even thought myObject doesn't modify it's value, the datagrid on the View selects other object. How can I prevent this?
View:
<DataGrid ItemsSource="{Binding MyObjectList}" SelectedItem="{Binding MyObjectSelected, Mode=TwoWay}">
ViewModel:
private ObservableCollection<MyObject> myObjectList;
private MyObject myObjectSelected;
private bool canSelectOtherObject;
public ObservableCollection<MyObject> MyObjectList
{
get { return myObjectList; }
set { myObjectList = value; }
}
public MyObject MyObjectSelected
{
get { return myObjectSelected; }
set
{
if(canSelectOtherObject)
{
myObjectSelected = value;
OnPropertyChanged("MyObjectSelected");
}
}
}
Thanks!
INotifyPropertyChanged is used to notify the UI to update bindings when the properties of an object change, I think you are describing a situation where the object itself changes.
Given your binding:
<DataGrid ItemsSource="{Binding MicrophoneList}" SelectedItem="{Binding MicrophoneSelected, Mode=TwoWay}">
It's the difference between updating one of the properties of the selected microphone (would require INotifyPropertyChanged), and changing SelectedItem to a different microphone altogether (binding updates whether you notify or not).
I have two user controls, one contains a TreeView, one contains a ListView.
The TreeView has an itemsource and hierarchical data templates that fill the nodes and leafes (node=TvShow, leaf=Season).
The ListView should show the children of the selected TreeView item (thus, the selected season): the episodes of that season.
This worked fine when I had both the TreeView and the Listview defined in the same window, I could use something like this:
<ListView
x:Name="_listViewEpisodes"
Grid.Column="2"
ItemsSource="{Binding ElementName=_tvShowsTreeView, Path=SelectedItem.Episodes}">
How can I achieve this, when both controls are defined in separate user controls? (because in the context of one user control, I miss the context of the other user control)
This seems something pretty basic and I am getting frustrated that I can't figure it out by myself. I refuse to solve this with code-behind, I have a very clean MVVM project so far and I would like to keep it that way.
Hope that somebody can give me some advise!
First of all you have to created the SelectedValue proeprty in your ViewModel and bind the TreeView.SelectedItem property to it. Since the SelectedItem property is read-only I suggest you to create a helper to create OneWayToSource-like binding. The code should be like the following:
public class BindingWrapper {
public static object GetSource(DependencyObject obj) { return (object)obj.GetValue(SourceProperty); }
public static void SetSource(DependencyObject obj, object value) { obj.SetValue(SourceProperty, value); }
public static object GetTarget(DependencyObject obj) { return (object)obj.GetValue(TargetProperty); }
public static void SetTarget(DependencyObject obj, object value) { obj.SetValue(TargetProperty, value); }
public static readonly DependencyProperty TargetProperty = DependencyProperty.RegisterAttached("Target", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null));
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached("Source", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null, OnSourceChanged));
static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
SetTarget(d, e.NewValue);
}
}
The idea is simple: you have two attached properties, the Source and the Target. When the first one changes the PropertyChangedCallback is called and you simply setting the NewValue as the Target property value. In my opinion this scenario is helpful in a lot of cases when you need to bind the read-only property in XAML (especially in control templates).
I've created a simple model to demonstrate how to use this helper:
public class ViewModel : INotifyPropertyChanged {
public ViewModel() {
this.values = new ObservableCollection<string>()
{
"first",
"second",
"third"
};
}
ObservableCollection<string> values;
string selectedValue;
public ObservableCollection<string> Values { get { return values; } }
public string SelectedValue {
get { return selectedValue; }
set {
if (Equals(selectedValue, values))
return;
selectedValue = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedValue"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So, we have data source, selected value and we'll bind it like this:
<StackPanel>
<TreeView ItemsSource="{Binding Values}"
local:BindingWrapper.Source="{Binding SelectedItem, RelativeSource={RelativeSource Self}, Mode=OneWay}"
local:BindingWrapper.Target="{Binding SelectedValue, Mode=OneWayToSource}"
>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Header" Value="{Binding}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<TextBlock Text="{Binding SelectedValue}"/>
</StackPanel>
In the TreeView bound to the ItemsSource from the ViewModel I've created two bindings so they are changing the SelectedValue property in your ViewModel. TextBlock in the end of the sample is used just to show that this approach works.
About the very clean MVVM - I think that it is not the same as the "no code-behind". In my sample the ViewModel still doesn't know anything about your view and if you'll use another control to show your data e.g. ListBox you will be able to use the simple two-way binding and the "BindingWrapper" helper will not make your code unreadable or unportable or anything else.
Create a SelectedSeason property in your ViewModel and bind the ListView's ItemsSource to SelectedSeason.Episodes.
In a perfect world, you could now use a Two-Way binding in the TreeView to automatically update this property when the SelectedItem changes. However, the TreeView's SelectedItem property is readonly and cannot be bound. You can use just a little bit of code-behind and create an event handler for the SelectionChanged event of the TreeView to update your ViewModel's SelectedSeason there. IMHO this doesn't violate the the MVVM principles.
If you want a pure XAML solution, that a look at this answer.
Cannot get my listview to display data.
XAML
<Grid>
<DockPanel>
<ListView Name="lstDetectedComputers"
MinWidth="200"
DockPanel.Dock="Left"
ItemsSource="{Binding ComputersList}" DisplayMemberPath="ComputerName">
</ListView>
<DataGrid x:Name="ViewNetworkCardInformation"
ItemsSource="{Binding NetworkCardInformation}"/>
</DockPanel>
</Grid>
Code:
private ObservableCollection<Object> _ComputersList;
public ObservableCollection<Object> ComputersList
{
get
{
return _ComputersList;
}
set
{
_ComputersList = value; NotifyPropertyChanged("ComputersList");
}
}
private DataTable _NetworkCardInformation;
public DataTable NetworkCardInformation
{
get
{
return _NetworkCardInformation;
}
set
{
_NetworkCardInformation = value; NotifyPropertyChanged("NetworkCardInformation");
}
}
Strange thing is that NetworkCardInformation shows in my datagrid so this indicates that the datacontext is working as expected.
now im under the impression with a ObservableCollection i do not need a INotifyPropertyChange, if this is wrong please advised.
i have also tried just ItemsSource="{Binding ComputersList}"
I have put a break point into the code to ensure that the observable collection has data, and it is there .
ComputersList Count = 2 System.Collections.ObjectModel.ObservableCollection
[0] {AdminUltimate.Model.NetworkModel.ComputerNode} object {AdminUltimate.Model.NetworkModel.ComputerNode}
ComputerName "ASUS-PC" string
Could someone please assist.
Thank you
You have set DisplayMemberPath as ComputerName but Object doesn't have any such property so it shows nothing on view.
This can be validated by removing DisplayMemberPath, you will see fully qualified class name of your object since ToString() gets called on your object if no ItemTemplate and DisplayMemberPath is set on ListBox.
So, solution would be to change ObservableCollection<Object> to type of more concrete object containing property ComputerName i.e. ObservableCollection<ComputerNode>.
I have a simple combobox with a checkbox inside as such:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="158,180,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" ItemsSource="{Binding collection}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Name}"></CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The datacontext is simply the code behind, and to test it I use the following code:
public ObservableCollection<Foo> collection { get; set; }
private void button1_Click(object sender, RoutedEventArgs e)
{
collection = new ObservableCollection<Foo>();
this.comboBox1.ItemsSource = collection;
Foo f = new Foo("DSD");
collection.Add(f);
}
When I set the ItemsSource as I have in the code, then it works fine, but I want to set the ItemsSource in the Xaml, however it does not work using the Xaml above. I have also tried setting it to Path = "". Anybody know why?
Thanks
You need to assign DataContext to the control. something like:
var window = new Window1();
window.DataContext = new WindowDC();
window.Show();
where Window1 class contains the combobox, and WindowDC is like:
public class WindowDC
{
public ObservableCollection<Foo> collection { get; set; }
}
That's how this will work.
What you actually do is that you place collection into control class, and set your datacontext for combobox only.
But, for testing purposes, you can still set Combox.Datacontext in control constuctor.
Bindings in WPF always have a Source. If you don't specify the source in the binding itself, then it will implicitly use the DataContext of the control or an ancestor of it. So if you want to bind to properties in your codebehind file, you have to set the DataContext to an object of the class which contains the collection property. In your case this is the instance of the Window (this).
DataContext = this;
As the commentor pointed out, it's not considered good style putting business logic or data inside the code behind file. So consider writing a separate class which contains your collection property and which you can use to initalize your DataContext. If you are writting bigger applications you should take a look at patterns like MVVM, which uses databinding to provide a better separation between your view and your model.
Edit: Changed ordering and incorporated feedback
Make sure there exist a public property collection in your code behind.
in the code behind also do this.DataContext = this
Finally implement INotifyPropertyChanged to tell the view that you have changed the collection once you add items in it
public ObservableCollection<Foo> Collection
{
get
{
return collection;
}
set
{
collection = value;
OnPropertyChanged("Collection");
}
private void button1_Click(object sender, RoutedEventArgs e)
{
collection = new ObservableCollection<Foo>();
//this.comboBox1.ItemsSource = collection;
Foo f = new Foo("DSD");
collection.Add(f);
OnPropertyChanged("Collection");
}
It is working when you are setting combo's item source in code behind because the source of combo is getting updated like wise to set the item source in XAML you have to make a property with INotifyPropertyChanged that keep update the combo's itemsource every time you update your collection via this property..
private ObservableCollection<Foo> _Collection;
public ObservableCollection<Foo> Collection
{
get
{
return collection;
}
set
{
collection = value;
OnPropertyChanged("Collection");
}
Now as you are filling collection on button click you just have to set that collection in the property as..
_Collection = new ObservableCollection<Foo>();
Foo f = new Foo("DSD");
_Collection .Add(f);
Collection = _Collection ; //here property call OnPropertyChange
like wise you can provide data to any control. It is jsut the game of INotifyPropertyChanged property.
Hope this will help you
I have a combo box that is bound to a list of model objects. I've bound the combo box SelectedItem to a property that is the model type. All of my data binding works beautifully after the window has been loaded. The SelectedItem is set properly and I'm able to save the object directly with the repository.
The problem is when the window first loads I initialize the SelectedItem property and my combobox displays nothing. Before I moved to binding to objects I was binding to a list of strings and that worked just fine on initialization. I know I'm missing something but I can't figure it out.
Thanks in advance for any guidance you can provide.
(One note about the layout of this page. The combo boxes are actually part of another ItemTemplate that is used in a ListView. The ListView is bound to an observable collection in the main MV. Each item of this observable collection is itself a ModelView. It is that second ModelView that has the SelectedItem property.)
Here is my Model:
public class DistributionListModel : Notifier, IComparable
{
private string m_code;
private string m_description;
public string Code
{
get { return m_code; }
set { m_code = value; OnPropertyChanged("Code"); }
}
public string Name
{
get { return m_description; }
set { m_description = value; OnPropertyChanged("Name"); }
}
#region IComparable Members
public int CompareTo(object obj)
{
DistributionListModel compareObj = obj as DistributionListModel;
if (compareObj == null)
return 1;
return Code.CompareTo(compareObj.Code);
}
#endregion
}
Here the pertinent code in my ModelView:
public MailRoutingConfigurationViewModel(int agencyID)
: base()
{
m_agencyID = agencyID;
m_agencyName = DataManager.QueryEngine.GetAgencyName(agencyID);
IntializeValuesFromConfiguration(DataManager.MailQueryEngine.GetMailRoutingConfiguration(agencyID));
// reset modified flag
m_modified = false;
}
private void IntializeValuesFromConfiguration(RecordCheckMailRoutingConfiguration configuration)
{
SelectedDistributionList = ConfigurationRepository.Instance.GetDistributionListByCode(configuration.DistributionCode);
}
public DistributionListModel SelectedDistributionList
{
get { return m_selectedDistributionList; }
set
{
m_selectedDistributionList = value;
m_modified = true;
OnPropertyChanged("SelectedDistributionList");
}
}
And finally the pertinent XAML:
<UserControl.Resources>
<DataTemplate x:Key="DistributionListTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</UserControl.Resources>
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource}, Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="False"
/>
#SRM, if I understand correctly your problem is binding your comboBox to a collection of objects rather than a collection of values types ( like string or int- although string is not value type).
I would suggest add a two more properties on your combobox
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource},
Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
SelectedValuePath="Code"
SelectedValue="{Binding SelectedDistributionList.Code }"/>
I am assuming here that DistributionListModel objects are identified by their Code.
The two properties I added SelectedValuePath and SelectedValue help the combobox identify what properties to use to mark select the ComboBoxItem by the popup control inside the combobox.
SelectedValuePath is used by the ItemSource and SelectedValue by for the TextBox.
don't call your IntializeValuesFromConfiguration from the constructor, but after the load of the view.
A way to achieve that is to create a command in your viewmodel that run this method, and then call the command in the loaded event.
With MVVM light toolkit, you can use the EventToCommand behavior... don't know mvvm framework you are using but there would probably be something like this.