WPF ListBox ListBoxItem Binding - wpf

I am going through the Sams book "Teach Yourself WPF in 24 Hours". At one point the authors show how you can bind a ListBox's selected-item value to a property. I get that, it's pretty straightforward. But when I try to create my own ListBox control with my own ListBoxItems, I can't seem to get it to work.
The ListBox that works uses a system collection as its ItemsSource property:
<ListBox x:Name="FontList"
DockPanel.Dock="Left"
ItemsSource="{x:Static Fonts.SystemFontFamilies}"
Width="160" />
The value selected from this ListBox is then used in a TextBlock as follows:
<TextBlock Text="Test"
FontFamily="{Binding ElementName=FontList, Path=SelectedItem}"
TextWrapping="Wrap"
Margin="0 0 0 4" />
Notice that the Path is set to SelectedItem.
Now, I wanted to set the FontSize using another ListBox that contains 3 different sizes. Here is what I did:
<ListBox x:Name="Size" >
<ListBoxItem>10</ListBoxItem>
<ListBoxItem>15</ListBoxItem>
<ListBoxItem>20</ListBoxItem>
</ListBox>
And then I added a binding to the Size attribute of the TextBox as follows:
<TextBlock Text="Test"
FontFamily="{Binding ElementName=FontList, Path=SelectedItem}"
Size="{Binding ElementName=Size, Path=SelectedItem}"
TextWrapping="Wrap"
Margin="0 0 0 4" />
The Size doesn't change when I run the program. So I tried to add the binding I was using for Size to the Text attribute--in order to see its value:
<TextBlock Text="{Binding ElementName=Size, Path=SelectedItem}""
FontFamily="{Binding ElementName=FontList, Path=SelectedItem}"
Size="{Binding ElementName=Size, Path=SelectedItem}"
TextWrapping="Wrap"
Margin="0 0 0 4" />
I see that it is changing as I click the Size ListBox, but I also see that the SelectedItem is displaying as this (when I click the 15 entry):
System.Windows.Controls.ListBoxItem:15
My questions:
1) What is the actual value being returned by the Path called SelectedItem? Is it "System.Windows.Controls.ListBoxItem:15" or is it "15"? If it's not 15, how can I specify a Path that returns just 15 and not System.Windows.Controls.ListBoxItem:15?
2)Why does the FontFamily SelectItem work? I realize that the FontList is coming from a System collection of font names, but it is unclear to me why the ListBox isn't returning a collection of ListBoxItems as text. If my ListBox's Path reference is returning a SelectedItem object of type ListBoxItem, then I would think I could use a Path of SelectedItem.Value or something like that--but it doesn't work and there is no Intellisense to help me.
I want to get THIS example working because it will help clear-up some misunderstandings I have. Please don't refactor the solution to get it to work some other way unless it's entirely impossible for me to have a Path reference that will give me just the numeric portion of my Size ListBoxItem that is selected.

What is the actual value being returned by the Path called SelectedItem?
It is System.Windows.Controls.ListBoxItem:15 (you can read this as "ListBoxItem with content set to 15"), that's why your binding does not work - it expects a numeric value, not ListBoxItem. You can specify Path as SelectedItem.Content to make this work. Also you can set SelectedValuePath of ListBox "Size" to "Content", and bind to SelectedValue property instead of SelectedItem.
Solution 1:
<TextBlock Size="{Binding ElementName=Size, Path=SelectedItem.Content}" />
Solution 2:
<ListBox x:Name="Size" SelectedValuePath="Content" />
<TextBlock Size="{Binding ElementName=Size, Path=SelectedValue}" />
Why does the FontFamily SelectItem work?
Because that ListBox contains a font collection, not a collection of ListBoxItems (they are still created to represent each item in a collection though). You can achieve the same behavior with font sizes if you define collection of font sizes in code and bind ListBox'es ItemsSource property to that collection or define contents of your ListBox as a collection of System.Double values directly in XAML:
<ListBox x:Name="Size"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:Double>10</system:Double>
<system:Double>15</system:Double>
<system:Double>20</system:Double>
</ListBox>

1) The actual value being returned by your SelectedItem binding is a ListBoxItem object. To get the value (15) from your binding you could use a converter or make your binding path a bit more explicit to get the listbox item's Content property value:
Size="{Binding ElementName=Size, Path=SelectedItem.Content}"
2) This is a covariant operation so the type of each list item is inferred from its source. The items generated by your font family items control (ListBox) are a result of the collection it's bound to. The Items property (populated via the ItemsSource dependency property) is an ItemCollection of generic objects which take on the type of their corresponding contextual objects.

Related

Why ItemTemplate will make items disappear for ListView?

I was solving Windows Phone 8.1 ListView wobbling problem and had code like below, however, once I add the ItemTemplate, the contents of the List cannot be seen, I'm wondering why and how to fix the problem.
<ListView
Grid.Row="1"
x:Name="ListViewEvents"
Loaded="OnListViewEventsLoaded"
ItemsSource="{Binding xx}"
ItemTemplateSelector="{StaticResource xx}"
ItemContainerStyle="{StaticResource xx}"
IsItemClickEnabled="True">
<ListView.ItemTemplate >
<DataTemplate >
<Grid Width="{Binding ActualWidth, ElementName=EventsListGrid}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Your Width binding is trying to read the ActualWidth property of an element in your XAML named EventsListGrid, but there's no such element in the sample code you've provided. As such, the binding engine is unable to set the Width property on your grid, most likely setting it to some unset/NaN value. At least I can confirm this when setting up a similar case as the one you provided and inspecting in Snoop the ListViewItem containers generated for each item in the test collection bound to the ListView. Perhaps you want to set the element name to ListViewEvents in this case or some other parent element not shown in the example?

Notify of a model property changed on a view in the DevExpress datagrid

I'm working on a WPF application using DevExpress, and also using the mvvm pattern, and have the following problem:
I have a view model that has a boolean property (for instance IsChecked)
I have a view that is a devexpress data grid that is bound to a collection of the above mentioned view model items
A row of that devexpress data grid is of type check box, and it's bound to the IsChecked property
I have a data template for the row of devexpress grid for showing a line in the row, if IsChecked is true
All this works fine when I check/uncheck the check box column in the data grid.... The problem is when I change the model property's value: the view does not change....
The model implements INotifyPropertyChanged.
It seems to be that DevExpress makes a wrapper for each item, and then does not notify the view when the model's property has changed.
After search into the DevExpress's Support Center, I could not find anything that helps me to solve this issues. But thanks to this nice application (Snoop) I get solve it.
The thing is that the DevExpress's grid collection do not behave like the other WPF's standard collections. In any WPF's collection each row (or item in a collection) has in the DataContext the ViewModel (or any class of the collection type). The DevExpress's grid do not works like that...
Each GridRow item of the DevExpress's grid has an object of type RowData in the DataContext, and each cell (are of type GridCellContentPresenter) has an object of type EditGridCellData. As we can see this objects are not the same type of our collection.
So how we can do any two-way binding between our view model, and our view row or cell item in the grid?
If we are making a row template:
The object that is in the DataContext in each row is of type RowData. This type have a property named DataContext, this property is of type RowTypeDescriptor (like a wrapper of our collection type). This do not works for making our binding. BUT the type RowData has a property namde Row in which we can find our collection's type row object, so the only thing that we have to do is to make a binding to this property. For instance, in this case we want that a line cross my row if a bool field is set to true:
<DataTemplate x:Key="myRowTemplate">
<Grid>
<dx:MeasurePixelSnapper>
<ContentPresenter x:Name="defaultRowPresenter" Content="{Binding}" ContentTemplate="{Binding View.DefaultDataRowTemplate}" />
</dx:MeasurePixelSnapper>
<Path Data="M5.496,10.5 L508,10.5" Fill="#FFF" HorizontalAlignment="Stretch" Height="1" Stretch="Fill" Stroke="#FF040404" VerticalAlignment="Center" Width="Auto" Margin="0" Visibility="{Binding Row.AnyModelBoolProperty, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IntToVisibilityConverter}}"/>
</Grid>
</DataTemplate>
If we are making a cell template:
In this case is a bit more complex. because the object EditGridCellData has not any property where we can find our collection's object type. The only we can find here is a property called Data in which we can find a object of type RowTypeDescriptor, so do not work fine. We need to solve this using a ancestor type binding. See this example:
<dxg:GridColumn x:Name="dateRow" FieldName="Date" Header="Date">
<dxg:GridColumn.CellTemplate>
<DataTemplate>
<dxe:DateEdit x:Name="PART_Editor" DateTime="{Binding DataContext.Row.Date, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type dx:StackVisibleIndexPanel}}}" IsEnabled="{Binding DataContext.Row.DateEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type dx:StackVisibleIndexPanel}}}" MinValue="{Binding DataContext.Row.DateMinValue, RelativeSource={RelativeSource AncestorType={x:Type dx:StackVisibleIndexPanel}}}"/>
</DataTemplate>
</dxg:GridColumn.CellTemplate>
</dxg:GridColumn>
Here we have a date control that will be bound to the model and will be correctly notified. Hope this could be useful for every one that had this problem. It is a real head ache.

How does wpf/databinding resolve this ambiguity?

<StackPanel DataContext="{StaticResource Employees1}">
<ListBox ItemsSource="{Binding}" DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True"/>
<Label Content="{Binding Path=Count}"/>
<Label Content="{Binding Path=Name}"/>
</StackPanel>
How does the binding for the labels get resolved? How is it decided that the content of the second label is bound to Employees1.Count (and not to Employee.Count), while the first label is bound to
Employee.Name and synchonized with the listbox selection? Also, what if I would like to bind the first label to Employee.Count instead?
(Employee has properties Name (and possibly Count), Employees1 is an ObservableCollection of type Employee).
EDIT: So, the question here is WHY the first label displays the number of employees in the ObservableCollection, while the second label displays the name of a specific employee in the collection, the one that is currently selected in the ListBox. Apparently, the first label binds to the entire collection, and the second label to a specific employee in the collection. But why, and how to control this behavior.
From MSDN Data Binding Overview, Binding To Collections, section "Current Item Pointer":
Because WPF binds to a collection only by using a view (either a view
you specify, or the collection's default view), all bindings to
collections have a current item pointer.
and section "Master-Detail Binding Scenario":
This works because when a singleton object (the ContentControl in this
case) is bound to a collection view, it automatically binds to the
CurrentItem of the view.
In your example, the second Label automatically binds to the current item of the default view of the Employees1 collection. The first Label would also bind like this, but since the item object does not have a Count property it apparently falls back to a binding to the Count property of the collection itself. However i don't know if the latter behaviour is documented somewhere.
As Blam says - the labels have no relationship to the listbox - I think what you're trying to do here is bind an observableCollection of Employees with properties Count and Name to the listbox..
To do this you'll need an ItemsTemplate in the listbox
<ListBox ItemSource={Binding Employees1}>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Count}" />
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Let me try and answer the questions.
An ObservableCollection has a property Count.
As for getting a single property on the second label it is making some assumptions.
You might not get the same behavior in other version of .NET.
Should not bind a control that displays a single value to a collection.
If you want the selected item from the ListBox see this link
enter link description here

Bind a ComboBox to two DataContexts

I have a ComboBox in my wpf application.
It's ItemsSource is binded to some table in my DataSet.
I need the text property to be binded to another's object property . I doesn't work because the ComboBox doesn't want to get two DataContexts. How can I solve this problem?
<StackPanel Width="Auto" Height="Auto" MinWidth="296" Orientation="Vertical" x:Name="MyStackPanel">
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding}" Text={Binding Path=MyProperty} />
</StackPanel>
In the code behind :
MyComboBox.DataContext = MyDataSet.Tables[MyTable];
MyStackPanel.DataContext = MyObject;
I want the ComboBox to show items from one DataContext but to show the text from another DataContext. How can I do it?
Don't use DataContext. Set the Source property of your bindings in XAML or create the bindings in code and set the Source property there.
Why are you assigning something to the datacontext of the stackpanel? From the looks of it, its not used.
Your code should work if MyDataSet.Tables[MyTable] returns an enumeration and contains a property called MyProperty.
What do you mean when you say that the combobox "doesn't want to get two DataContexts"?
Look into the properties IsEditable and IsReadOnly of the combobox.
Something like
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding}" Text={Binding ElementName=MyStackPanel Path=DataContext.MyProperty} />

Bind datagrid to one ViewModel, column / combobox to another

I a have a View Players, the datacontext is set to a ObservableCollection Players from the ViewModel MainPlayerViewModel.
In the View I have a datagrid with columns TeamId, Name and Position.
I want to bind the TeamId column with a combobox to a list of available teams from the MainTeamViewModel which has a collection property Teams but of course I want the MainPlayerViewModel to be updated whenever I update the team for a player.
I hope you can follow me here..
This is my xaml:
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DataContext="{Binding MainTeam, Mode=OneWay, Source={StaticResource Locator}}"
Height="23" HorizontalAlignment="Left"
Name="cmbTeams" VerticalAlignment="Top" Width="100" ItemsSource="{Binding Teams,
Mode=TwoWay}" SelectedValue="{Binding Path=Model.teamid, Mode=TwoWay}"
DisplayMemberPath="Model.teamid"/>
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
When I edit the cell it shows the list of available teams but the selectedvalue I pick from the list doesn't turn up in the TeamId column
How do I pull this off?
Kind regards,
Mike
UPDATE:
Despite the help I received I didn't get it to work binding one View to 2 different Viewmodels.
Guess the solution offered is long above my head..
I couldn't set the datacontext of the datagrid to MainTeam because it has an ItemsSource of players and a selecteditem bound twoway to selectedplayer.
Anyway I decided to keep it 1 View / 1 ViewModel and created a public property on my PlayerViewModel named teamsVM:
public MainTeamViewModel teamsVM
{
get
{
return ViewModelLocator.Container.Resolve<MainTeamViewModel>();
}
}
Now I can set the Itemsource to this new property and my player row get's updated when I change teams:
<DataTemplate>
<ComboBox
Height="23" HorizontalAlignment="Left"
Name="cmbTeams" VerticalAlignment="Top" Width="100"
ItemsSource="{Binding teamsVM.Teams,
Mode=TwoWay}" SelectedValue="{Binding Model.teamid, Mode=TwoWay}"
DisplayMemberPath="Model.teamid" SelectedValuePath="Model.teamid"/>
</DataTemplate>
Regards,
Mike
I find two things wrong with this code.
You are missing the SelectedValuePath for the ComboBox. Even though you bind all teams to it, the selected item's id is null because the SelectedValuePath is missing.
You also have a DataContext and an ItemsSource. Use only the ItemsSource for the teams you want to display, and the SelectedValue to be bound to the player's teamId, unless your view model has a "Teams" property and a "Player" property, in which case the DataContext may be used. (Id set the DataContext in code though...)
So yo will end up with something like this:
ItemsSource="{Binding Teams, Mode=TwoWay}" //Bind to all teams.
SelectedValue="{Binding Player, Path=TeamId, Mode=TwoWay}" //Bind to the teamId of the player.
DisplayMemberPath="TeamName" //that's the Name of each team.
SelectedValuePath="TeamId" //that's the Id of the team.
Two problems here:
First, as #bleepzer noted you did not specify the value/display paths in your combo box.
Second, you trying to access a property in the data context that is outside your grid (i.e. the main view model's data context) from within a data template. In silverlight 4 there is no relative source binding (something you would use in SL 5 or WPF), so you will have to use element binding to archive what you want.
Here is an example based on your code. It is not complete as it leaves out some of the DataGrid elements needed, but it shows the concept:
<data:DataGrid x:Name="myDataGrid"
DataContext="{Binding MainTeam, Mode=OneWay, Source={StaticResource Locator}}" >
<!-- additional stuff needed here -->
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Height="23" HorizontalAlignment="Left"
Name="cmbTeams" VerticalAlignment="Top" Width="100"
ItemsSource="{Binding ElementName=myDataGrid, Path=DataContext.Teams}"
SelectedValuePath="TeamId"
DisplayMemberPath="TeamName"
SelectedValue="{Binding Path=Model.teamid, Mode=TwoWay}"/>
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
<!-- additional stuff needed here -->
<data:DataGrid>
And here is the description:
Add a name to your data grid.
Make sure the data grid has the right data context, either by setting it explicitly as in the sample, or inheriting it from the parent hierarchy.
Modify your ComboBox's ItemsSource property to point to the data grid using the element name you specified earlier. As you are now on the element and not on the data context you have to use DataContex.Teams to access the Teams property on the data context of your grid. The ItemsSource does not need two-way-binding as the view does not write anything back to your view model.
Specify the SelectedValuePath and DisplayMemberPath properties.
Finally, bind the SelectedValue property of the combo box to your rows model TeamId property using two-way-binding - needed now as the view should update the model's value. Important: the SelectedValue property of the combo box has to be bound after the ItemsSource to prevent some problems with the combo box.

Resources