I am following MVVM in C# and am attempting to display a view in a listbox.
I am setting the listbox itemsource (in code, not in binding and using the viewmodels collection) and then set the datatemplate to be my view in xaml. The issue I'm encountering is my view always loads with its default constructor values, if I remove the datacontext from the view however it loads fine.
Below is the listbox I am creating in xaml
<ListBox Name="lbCatalogues" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<view:CatalogueRowView/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is the xaml for my view. If I remove the DataContext it works
<UserControl.DataContext>
<model:CatalogueModel />
</UserControl.DataContext>
<Grid Name="Container" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<!-- Catalogue_ID, UploadedTime, Client_ID, Name, Desc, Filesize -->
<Label Name="lblCatalogueID" Content="{Binding Path=CatalogueID}" Grid.Column="0"/>
<Label Name="lblUploadedTime" Content="{Binding Path=UploadedTime}" Grid.Column="1"/>
<Label Name="lblCatalogueName" Content="{Binding Path=Name}" Grid.Column="2"/>
<Label Name="lblCatalogueDescription" Content="{Binding Path=Description}" Grid.Column="3"/>
<Label Name="lblFilesize" Content="{Binding Path=Filesize}" Grid.Column="4"/>
<Grid/>
This is the code where I am setting the listbox ItemSource:
lbCatalogues.ItemsSource = catalogueViewModel.Records;
My question is how can I get the view to load correctly within the listbox so that each item in the listbox has a DataContext linked to that listbox Itemsource?
You already know the answer: simply remove <UserControl.DataContext> from your UserControl
You are telling your UserControl to use a new instance of CatelogueModel for the DataContext, and this overrides any DataContext that is set when you use your UserControl. See MSDN's list of Dependency Property Precedence for more info
I do not ever recommend setting the DataContext inside a UserControl. It goes against how WPF is meant to work by having separate UI and data layers, and is a problem for anyone trying to use your UserControl
As for your question about each item in the ListBox linking to the ItemsSource, DataTemplates simply tell WPF how to draw an object. The data behind the object still remains.
For example, your ListBox contains a list of Record objects, and your DataTemplate is telling the ListBox to draw each one of those records with CatelogueRowView. The actual data behind the CatelogRowView is still your data object from catelogueViewModel.Records
lbCatalogues.ItemsSource = catalogueViewModel.Records; instead of this Simply bind ItemsSource of ListBox as ItemsSource="{Binding Records}" in xaml. I hope this will help.
Related
I'm new to WPF, but am pretty familiar with binding list box controls to observable collections in the view model.
In my current project we have a ListBox that is used for navigating to different pages in a frame box. I want to add some display information to the first ListBoxItem to show which object (in this case, the Scenario) is being worked on (it is selected in a previous frame that visible in the subsequent frames). The ListBox itself is using a static list defined in the xaml, so it isn't bound to anything in the ViewModel. The CurrentScenario is a property on the ViewModel. I was able to add a Label to the same window that contains this ListBox and successfully bind CurrentScenario.Id to its content, and it updated correctly, so I know that the path in the Binding statement should resolve correctly.
<ListBox
Style="{StaticResource FunctionBackground}"
IsSynchronizedWithCurrentItem="True"
>
<ListBoxItem Style="{StaticResource FunctionListBoxItemStyle}">
<ListBoxItem.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Scenario" />
<TextBlock Grid.Row="1" Text="{Binding Path=CurrentScenario.Id}"/>
</Grid>
</DataTemplate>
</ListBoxItem.ContentTemplate>
</ListBoxItem>
<ListBoxItem Style="{StaticResource FunctionListBoxItemStyle}" >Parameter</ListBoxItem>
<ListBoxItem Style="{StaticResource FunctionListBoxItemStyle}" >Run</ListBoxItem>
<ListBoxItem Style="{StaticResource FunctionListBoxItemStyle}" >Results</ListBoxItem>
</ListBox>
When I try to add this extra information to the listbox item, from what I can tell, the list box item has an empty text block below the text block with the word "Scenario." I can't figure out why the empty text box content is not showing the value of the bound property. When I put a normal string in the Text property of the second text block, it shows up correctly.
I imagine that either ListBoxItem content is only set up be bound to properties related to the ItemSource, and so it ignores attempts to bind to other things, or maybe there is something fundamental in WPF that I am missing. Or both...
Thanks if anyone has any ideas!
So if property CurrentScenario is in ViewModel you can use RelativeSource to binding to this property.
...
<TextBlock Grid.Row="0" Text="Scenario" />
<TextBlock Grid.Row="1" Text="{Binding Path=DataContext.CurrentScenario.Id, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
...
<UserControl.Resources>
<DataTemplate x:Key="LstBoxTemplate">
<TextBlock Text="{Binding Item}" TextWrapping="Wrap" HorizontalAlignment="Left"/>
<Image Grid.Column="2" Margin="0,0,10,0" Visibility="{Binding isVisible,Converter={StaticResource ImageCtlVisibilityConverter}}" Source="/pjct;component/Images/im.png"/>
</DataTemplate>
</UserControl.Resources>
<ListBox x:Name=lstbox ItemsSource="{Binding itemList}" ItemTemplate="{StaticResource LstBoxTemplate}" />
You question needs more detail, and so I may be missing the point...
If your listbox is bound to a collection of custom objects that you control (aka a view model). Then it should be fairly straight forward for you to add a property to the view model that contains the object that you want to bind your listboxitem to...
I have a combobox defined as follows:
<ComboBox x:Name="cboDept" Grid.Row="0" Margin="8,8,8,8" HorizontalAlignment="Left" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Source={StaticResource cvsCategories}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Width="Auto" Height="Auto">
<sdk:Label Content="{Binding CategoryID}" Height="20" />
<sdk:Label Content="{Binding CategoryName}" Height="20" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
It works fine. However, once I select an item in the list, I want a different template to be applied to the combobox selected item being shown to the user (the item shown after the disappearance of popup). In the above case, I want only CategoryName to be displayed in the ComboBox once I select the respective item.
Can anyone let me know on how to achieve this?
thanks
What you need to do is create a ResourceDictionary containing a few defined templates yourself. In the below, ComboBoxTemplateOne and ComboBoxTeplateTwo are user controls that are set out to display the combobox in the manor you want.
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate x:Key="TemplateOne">
<local:ComboBoxTemplateOne />
</DataTemplate>
<DataTemplate x:Key="TemplateTwo">
<local:ComboBoxTemplateTwo />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
You will then need to create your own class that inherits from ContentControl "DataTemplateSelector", overriding OnContentChanged
Protected Overrides Sub OnContentChanged(ByVal oldContent As Object, ByVal newContent As Object)
MyBase.OnContentChanged(oldContent, newContent)
Me.ContentTemplate = SelectTemplate(newContent, Me)
End Sub
You will then need to create another class that inherits from the above DataTemplateSelector which overrides SelectTemplate ("TemplateSelectorClass"), which will return the DataTemplate defined above ("TemplateOne" or "TemplateTwo").
Also in this derived class, you will need to define a property for each of the templates you have
Public Property ComboboxTemplateOne As DataTemplate
Then head back to your XAML and n the blow XAML
<local:TemplateSelectorClass ComboboxTemplateOne="{StaticResource TemplateOne}" Content="{Binding Path=ActiveWorkspace}>
This should work, as it is effectively doing the same work as setting the "DataTemplate" property in WPF (which doesn't exist in SilverLight)
I realise there are a fair few steps here and its quite fiddly, but hopefully this will get you there. Any questions just shout.
I've noticed a behavior difference between static and dynamic resource on ComboBox.ItemsSource, when the ComboBox gets out the visual tree.
in the static exemple the selected destination remains
in the dynamic exemple, the underlying object gets a null value
Binding seems OK, because when the comboboxes gets in focus, and have their SelectedIndex changed, the change are properly notified to the other list - both objects implements INotifyProperty - and both List are ObservableCollections.
It's when the dynamic-bound combobox gets out of focus that the strange things happen
XAML
<Window ... xmlns:me = "clr-namespace:WpfComboBoxBug">
<Window.Resources>
<me:ShippingList x:Key="sl" />
<me:DestinationList x:Key="dl" />
</Window.Resources>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="21" />
<RowDefinition Height="421*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Custom:DataGrid Grid.Row="1"
ItemsSource="{StaticResource sl}" x:Name="dg" AutoGenerateColumns="False" Grid.RowSpan="2">
<Custom:DataGrid.Columns>
<Custom:DataGridTextColumn Header="Reference" Binding="{Binding Reference}" />
<Custom:DataGridTemplateColumn Header="Destination">
<Custom:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Destination.Name}"></TextBlock>
</DataTemplate>
</Custom:DataGridTemplateColumn.CellTemplate>
<Custom:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{StaticResource dl}" SelectedItem="{Binding Destination,Mode=TwoWay}" DisplayMemberPath="Name"/>
</DataTemplate>
</Custom:DataGridTemplateColumn.CellEditingTemplate>
</Custom:DataGridTemplateColumn>
</Custom:DataGrid.Columns>
</Custom:DataGrid>
<Custom:DataGrid Grid.Column="1" Grid.Row="1" ItemsSource="{StaticResource sl}" x:Name="dg2" AutoGenerateColumns="False" Grid.RowSpan="2">
<Custom:DataGrid.Columns>
<Custom:DataGridTextColumn Header="Reference" Binding="{Binding Reference}" />
<Custom:DataGridTemplateColumn Header="Destination">
<Custom:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Destination.Name}"></TextBlock>
</DataTemplate>
</Custom:DataGridTemplateColumn.CellTemplate>
<Custom:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{DynamicResource dynamicdl}" SelectedItem="{Binding Destination,Mode=TwoWay}" DisplayMemberPath="Name"/>
</DataTemplate>
</Custom:DataGridTemplateColumn.CellEditingTemplate>
</Custom:DataGridTemplateColumn>
</Custom:DataGrid.Columns>
</Custom:DataGrid>
<TextBox Height="23" Name="textBox1" VerticalAlignment="Top" Text="Static" />
<TextBox Height="23" Name="textBox2" VerticalAlignment="Top" Text="Dynamic" Grid.Column="2" />
</Grid>
</Window>
CS
using System;
/* snip */
namespace WpfComboBoxBug
{
/// <summary>
/// Logique d'interaction pour MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
ShippingList sl;
this.InitializeComponent();
sl = this.Resources["sl"] as ShippingList;
ResourceDictionary rd = new ResourceDictionary();
rd.Add("dynamicdl", this.FindResource("dl"));
dg2.Resources = rd;
dg.ItemsSource = CollectionViewSource.GetDefaultView(sl);
dg2.ItemsSource = CollectionViewSource.GetDefaultView(sl);
}
}
}
full source code at : http://dl.free.fr/eI1VtkaB8 ( VS 2008 SP1, .NET 3.5 SP1 )
I expected the dynamic resource to behave like the static resource in this case, beacause I intialize it once in the beginning.
Have I found a bug here ?
If that' not the case, how would you explain the difference ?
I'd have to look at your code to be sure, but I would guess that your ComboBox has its SelectedItem or SelectedValue two-way bound to a property.
When you use StaticResource the resource reference is resolved at XAML load time. When you use DynamicResource the resource refrence is resolved later. So what is probably happening is that your ComboBox is starting up with no items, which is forcing its SelectedItem and SelectedValue null. The two-way binding causes the property to update with this value.
Personally I consider ComboBox's inability to gracefully handle this situation to be a bug in the design of ComboBox, not an implementation bug.
For my own projects I frequently use a ComboBox and ListBox enhancement I created to fix this problem: I have additional properties I can use in place of SelectedValue and SelectedItems. My new properties accepts any value until the ItemsSource is set, after which it stays synchronized with SelectedValue or SelectedItem.
You could use a similar technique, or just always make sure ItemsSource is bound / initialized before SelectedValue or SelectedItem.
Update
When the control is removed from the visual tree, everything happens in reverse: ItemsSource is cleared immediately due to the ancestry change, then DataContext is cleared. During the interim, ComboBox has a null SelectedItem which is propagated to the bound property.
An enhanced ComboBox or ListBox class with additional SelectedItem and SelectedValue properites can also solve this: It should keep SelectedItem / SelecteValue in sync with the custom properties whenever ItemsSource is non-null and decouple them whenever ItemsSource is null.
I've modified my question since it has changed focus when trying things out.
I narrowed the problem down to the following...
I try to bind the selected Item of a TreeView to a StackPanel (or some other container that can hold User Controls). This container will then display a UserControl, depending on the type of the selected item.
Here is the xaml of the StackPanel (both treeview and stackpanel are in the same window ==> different grid column)
<StackPanel Grid.Column="2" MinWidth="500" DataContext="{Binding ElementName=myTree, Path=SelectedItem, Mode=OneWay}">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type mvTypes:MyTypeA}">
<controls:UserControlA DataContext="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type mvTypes:MyTypeB}">
<controls:UserControlB DataContext="{Binding}" />
</DataTemplate>
</StackPanel.Resources>
</StackPanel>
When I place a user control directly under the stackpanel (not in the resources), it displays it with the selected object as their datacontext.
Idem if I place a TextBox in it, it will show the correct type of the selected item.
<TextBox Name="textBox1" Text="{Binding}" />
For some reason, placing it within a DataTemplate (even without setting the DataType) results in nothing to display.
Any sugestions. I'm thinking that maybe a StackPanel is not the right control for this, though I can't seem to find other controls that look suitable as containers like this.
Thanks in advance.
Replace the StackPanel in your example with ContentPresenter and instead of DataContext set the Content property. That should work.
Although you have set the Binding on the second custom control, are you setting the DataContext, as the binding is the route to the information and the DataContext is the information it applies this binding information to.
Andrew
You can create a UserControl to display the TreeView and the selection info on the right, all in one. It saves you from creating any custom control. A custom control is basically unnecessary since you do not create anything which didn't exist before.
<UserControl x:Class="NameSpace.SelectionView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="namespace.Controls"
Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TreeView Name="customTree">
<!--Items go here-->
</TreeView>
<StackPanel Grid.Column="1" MinWidth="50" DataContext="{Binding ElementName=customTree, Path=SelectedItem, Mode=OneWay}">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type StylingTest:CustomViewModelA}">
<controls:CustomADetailsControl />
</DataTemplate>
<DataTemplate DataType="{x:Type StylingTest:CustomViewModelB}">
<controls:CustomBDetailsControl />
</DataTemplate>
</StackPanel.Resources>
<TextBlock Text="{Binding}"/>
</StackPanel>
</Grid>
</UserControl>
Any other custom behaviour, I'm sure you could create or set in styles/templates here.
Also, you might find one of my other answers useful.
Good luck with wpf, cheers.