Source versus DataContext in XAML - wpf

Which of these methods is best?
<Window.Resources>
<sys:Int16 x:Key="MyValue">123</sys:Int16>
</Window.Resources>
<StackPanel>
<!-- method 1 -->
<TextBlock Text="{Binding}" DataContext="{StaticResource MyValue}" />
<!-- method 2 -->
<TextBlock Text="{Binding, Source={StaticResource MyValue}}" />
</StackPanel>

As with many "which is better" questions. I would say that "it depends" on the context.
They both exist because they both can serve a purpose in different contexts. Given only what you have shown above, I would choose Example 2.
When you set the DataContext, however, all of its children will inherit that DataContext. So maybe instead you are using a Button. And within you Button, you want to jazz it up a bit and display the text four times each with a different color. As you can see below, I would then choose Example 1.
Example 1: (note the DataContext is on the button, and the TextBlocks don't need the Source like they do in Example 2)
<Button DataContext="{StaticResource MyValue}" Height="Auto" Width="Auto" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding}" Foreground="Red" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding}" Foreground="Blue" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding}" Foreground="Yellow"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding}" Foreground="Green" />
</Grid>
</Button>
Example 2:
<Button Height="Auto" Width="Auto" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding, Source={StaticResource MyValue}}" Foreground="Red" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding, Source={StaticResource MyValue}}" Foreground="Blue" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding, Source={StaticResource MyValue}}" Foreground="Yellow"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding, Source={StaticResource MyValue}}" Foreground="Green" />
</Grid>
</Button>
When you're binding to a simple object that only has one representation like an Int16 in your case, you're probably only going to bind and display that value once, and thus option 2 makes most sense.
A good rule of thumb... if you find yourself setting the 'Source' to the same thing more than one binding, you should probably just bind the DataContext of a common parent FrameworkElement.

I would say that if I had to choose between the two, I would go with method 2. DataContext is really more for Databinding an item to a more complex underlying object and eases the databinding of many data values.
Just out of curiosity, why are you doing it this way? Does your code change the value of MyValue at some point? Is there no better way for you to do it for some reason?

The DataContenxt DependencyProperty allows you to easily bind across all of proeprties for a DependencyObject.
The Source DependenceyProperty of a Binding allows you to point that specific binding to the source you want, regardless of the DataContext.
This becomes really helpful when you are doing more complex bindings for ListViews. For instance:
<Window.Resources>
<local:MyConverter x:Key="MyConverter" />
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Source={StaticResource MyConverter}, Path=DisplayValues}" DataContenxt={Binding ElementName=lvwItems Path=SelectedItem} SelectedItem="{Binding Converter={StaticResource MyConverter}"/>
<ListView Name="lvwItems"......
The above example just shows off that I set the itemssource to a property in the 'MyConverter' called DisplayValues, the datacontext is what I am working with on that combobox though, which is handling the SelectedItem property of the ListView named 'lvwItems'.
Hope this helps.

Related

Why does a String DataTemplate cause a stack overflow exception?

I have a couple of DataTemplates defined in the resources of a ContentControl:
<DataTemplate DataType="{x:Type sys:String}">
<Label Content="{Binding}" HorizontalContentAlignment="Center" />
</DataTemplate>
<DataTemplate DataType="{x:Type dmodels:CBClient}"> <!-- Client Details Template -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Client Details" Background="{StaticResource brush_Client}" Foreground="White" Margin="0,0,1,0" VerticalAlignment="Center" />
<TextBlock Grid.Column="1" Text="Id:" Background="{StaticResource brush_Client}" Foreground="White" />
<TextBlock Grid.Column="2" Text="{Binding Path=ClientId}" Background="{StaticResource brush_Client}" Foreground="White" Margin="0,0,1,0" />
<Button Grid.Column="3" Style="{StaticResource EditButton}" />
</Grid>
</DataTemplate>
If I return a CBClient object, I get the proper display from the defined DataTemplate.
If I return a string, I get a StackOverflowException.
If I eliminate the String DataTemplate and return a string, I get the string displayed, but not formatted as I want it.
What am I doing wrong? Do I have to wrap the basic string return into a full class, and expose a message property? Is there no way to combine complex objects with integral types for DataTemplates?
Thanks.
J
You introduced a complication by using a Label. It automatically generates a textblock if you bind to a string. Binding in that way has an odd effect if you rely on it automatically adding the textblock. I should think it's because it sees an object rather than a string from the type of the property you're binding.
You could just do:
<DataTemplate DataType="{x:Type sys:String}">
<Label HorizontalContentAlignment="Center">
<TextBlock Text="{Binding}" />
</Label>
</DataTemplate>

Multiple TabItems bound to same ViewModel are not updating the first time a tab is selected

I have a tabcontrol with 3 tabs on it. Each tabitem contains a different contentcontrol, all of which are bound to the same ViewModel, and there are some items in all 3 contentcontrols that are bound to the same property.
My problem is, if I change a comboBox on the initial tab, that change is not reflected on the other tabs, they still have their SelectedItem set to the default value. This only happens the first time I switch to that tab. After I have selected a tab once, everything works as expected; changes made on one tab are reflected on the other tab.
I am trying to avoid too much code-behind, and I have searched and tried all of the solutions regarding delaying of the binding, or updating the binding when the tab is selected, but I have had no luck.
Here is my TabControl:
<TabControl Grid.Row="1" Margin="0,10">
<TabItem Header="Tab1" IsSelected="True">
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource Tab1}" />
</TabItem>
<TabItem Header="Tab2">
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource Tab2}" />
</TabItem>
<TabItem Header="Tab3">
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource Tab}" />
</TabItem>
</TabControl>
And here is one of the ContentControls:
<DataTemplate x:Key="Tab3" DataType="{x:Type vm:MainViewModel}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="Flow" Style="{StaticResource tabLabel}" />
<TextBox Grid.Column="1" Grid.Row="0" Style="{StaticResource tabTextBox}" />
<ComboBox Grid.Column="2" Grid.Row="0" Style="{StaticResource tabUnitComboBox}" ItemsSource="{Binding DataList1}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedData1}" />
<ComboBox Grid.Column="0" Grid.Row="1" Style="{StaticResource tabComboBox}" ItemsSource="{Binding DataList2}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedData2}" />
<TextBox Grid.Column="1" Grid.Row="1" Style="{StaticResource tabTextBox}" />
<Label Grid.Column="2" Grid.Row="1" Content="m³/h" Style="{StaticResource tabUnitLabel}" Visibility="{Binding SelectedData2, Converter={StaticResource VisibilityConverter}}" />
<ComboBox Grid.Column="0" Grid.Row="2" Style="{StaticResource tabComboBox}" ItemsSource="{Binding DataList3}" SelectedItem="{Binding SelectedData3}" />
<TextBox Grid.Column="1" Grid.Row="2" Style="{StaticResource tabTextBox}" />
<ComboBox Grid.Column="2" Grid.Row="2" Style="{StaticResource tabUnitComboBox}" ItemsSource="{Binding DataList4}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedData4}" />
<Label Grid.Column="0" Grid.Row="3" Content="Temperature" Style="{StaticResource tabLabel}" />
<TextBox Grid.Column="1" Grid.Row="3" Style="{StaticResource tabTextBox}" />
<ComboBox Grid.Column="2" Grid.Row="3" Style="{StaticResource tabUnitComboBox}" ItemsSource="{Binding DataList5}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedData5}" />
</Grid>
</DataTemplate>
The ViewModel is pretty much standard, inheriting from ObservableObject and implementing OnPropertyChanged. As I said, everything works perfectly after selecting a tab once, but I would really like to get it working from the start.
Any suggestions would be greatly appreciated. Thanks.
In case you want to use that comment as and answer
Are you by any chance just starting with assigning the backing field.
Not firing INPC on the initial load.

Creating a Table in XAML and populating it with an Array?

If I have a table like the one below:
<Grid VerticalAlignment="Top" HorizontalAlignment="Left" ShowGridLines="True" Width="250" Height="100">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock FontSize="20" FontWeight="Bold" Grid.ColumnSpan="3" Grid.Row="0">2005 Products Shipped</TextBlock>
<TextBlock FontSize="12" FontWeight="Bold" Grid.Row="1" Grid.Column="0">Quarter 1</TextBlock>
<TextBlock FontSize="12" FontWeight="Bold" Grid.Row="1" Grid.Column="1">Quarter 2</TextBlock>
<TextBlock FontSize="12" FontWeight="Bold" Grid.Row="1" Grid.Column="2">Quarter 3</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="0">50000</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="1">100000</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="2">150000</TextBlock>
<TextBlock FontSize="16" FontWeight="Bold" Grid.ColumnSpan="3" Grid.Row="3">Total Units: 300000</TextBlock>
</Grid>
Would I be able to populate it in one go using an array.
For example, if I had an Array containing "Row 1", "Row 2" etc. thru 10 would I be able to populate the first column with those values?
I'm not sure I'm doing a great job of explaining. I know I could do each cell individually, but I want it to cycle through and do all at once?
Thanks
Firstly, consider just using a ListBox or ItemsControl, with a DataTemplate. Define the 2 header rows in a separate grid, and stack this one underneath. The catch with this approach is that you need to define fixed-width columns, since each row will be its own Grid (or actually StackPanel is more performant in this scenario):
<ItemsControl ItemsSource="{Binding TheArray}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width="100" Text="{Binding Col1}" />
<TextBlock Width="100" Text="{Binding Col2}" />
<TextBlock Width="100" Text="{Binding Col3}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Secondly, if you need to use a true Grid, then one approach could be to define a Behavior on your Grid. This Behavior class would define an Items dependency property. The dependency property's "changed" handler could then create the TextBlocks (x of them for each cell where x is the number of columns), add them to the Grid, and assign the Grid.Row and Grid.Column properties (and even add the RowDefinitions if necessary).
<Grid>
<i:Interaction.Behaviors>
<my:GridItemsBehavior Items="{Binding TheArray}" />
</i:Interaction.Behaviors>
</Grid>
I wouldn't necessarily recommend the latter approach, because you lose a lot of the power of XAML by creating UI in code-behind.

Make the last TextBox in a DataTemplate Stretch

I have an ItemsControl:
<Border Grid.Row="1" Margin="20" BorderBrush="AliceBlue" BorderThickness="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<ItemsControl Margin="10" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding SelectedEventHistoryEntryCollection}" ItemTemplateSelector="{StaticResource computerEventHistoryDataTemplateSelector}"/>
</Border>
With some datatemplates. I'm testing the first template:
<DataTemplate x:Key="DetailsDataTemplate">
<Grid>
<Label Width="150" HorizontalAlignment="Left" VerticalAlignment="Top" Content="{x:Static resx:Resources.Label_ServiceDept}"/>
<TextBox Margin="110,0,0,0" Width="200" IsReadOnly="True" Text="{Binding ServiceDepartment}" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Label Width="150" Margin="0,40,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="{x:Static resx:Resources.Label_SLA}"/>
<TextBox Margin="110,40,0,0" Width="200" IsReadOnly="True" Text="{Binding SLA}" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Label Width="150" Margin="0,80,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="{x:Static resx:Resources.Label_Details}"/>
<TextBox Margin="110,80,10,10" IsReadOnly="True" TextWrapping="Wrap" Text="{Binding Details}"/>
</Grid>
</DataTemplate>
I would like the last Textbox in the datatemplate to use up the remaining space, but nothing I tried works. How can I get this uncooperateive TextBox to stretch?
Edit: Removed the Height Property on the Textbox.
Change the grid to a DockPanel with LastChildFill="true".
You can then get rid of all of the Margins and let WPF do the layout automatically.
Use a <DockPanel> instead of a <Grid>.
The last item in the DockPanel uses remaining space.
Generally, I use <Grid.RowDefinitions> and <Grid.ColumnDefinitions> in conjunction with star sizing * instead of margins for this type of layout.
UPDATE 1: (Removed for clarity)
UPDATE 2: When I wind up in situations like this where I can’t figure out where to apply a binding or a template I try to back up and look at the problem differently. I almost always take it back to the MVVM pattern. In this case, your Model is your EventHistory object. Your ViewModel has an ObservableCollection<EventHistory>. And your View is simply binding to that collection. So, to get something like this:
You would use something like this for your View:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="8" />
<RowDefinition Height="1.5*" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="0" AutoGenerateColumns="True"
ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True"
HorizontalGridLinesBrush="DarkGray" VerticalGridLinesBrush="DarkGray" />
<GridSplitter Grid.Row="1"
Background="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Border Grid.Row="2" BorderBrush="DarkGray" BorderThickness="1" CornerRadius="3" Padding="8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Status" />
<TextBox Grid.Row="0" Grid.Column="1" Margin="0,0,0,8" Text="{Binding Path=Status}" />
<Label Grid.Row="1" Grid.Column="0" Content="Detailed Description" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Path=Description}" />
</Grid>
</Border>
</Grid>
And that is just fine -- because that is what you are trying to achieve. The bindings on the 2 labels and textboxes at the bottom of the screen don’t have to be part of any data template. They are part of the view (everything inside the red border in the screenshot). All of the resizing works and everything is good. If you really want to move things into a DataTemplate, it is probably possible, but this seemed more natural at this point.
NOTE: After creating the View (area inside the red border) I hosted it in the main window leaving an area to the right as per your screenshot. I also took a few liberties with a grid splitter, star resizing and margins so things would take up all of the available space while maintaining the pictured proportions.
Hopefully that helps!
I was a little slow on the uptake with my first answer. After realizing what you were after I don't think that approach was correct. Also, I don't think you can easily achieve what you're after using DataTemplates. However, I do think you have a few options:
Check into Prism since is is good at doing things like building composite applications. However, it seems like WAY overkill for this problem. So, a more direct approach may be...
Build out custom controls for each separate detail view you have and then write some custom logic to load each view as needed. You would set it up like this...
You main grid and your details view host (i.e. the ContentControl) would be set up something like this:
<Grid Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="8" />
<RowDefinition Height="1.5*" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="0" />
<GridSplitter Grid.Row="1" Background="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Border Grid.Row="2" BorderBrush="DarkGray" BorderThickness="1" CornerRadius="3" Padding="8">
<ContentControl Grid.Row="2" x:Name="myContent" />
</Border>
</Grid>
And each of your custom controls for your individual detail views would be set up something like this:
<UserControl x:Class="WpfApplication1.CustomUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Status" />
<Label Grid.Row="1" Grid.Column="0" Content="Description" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Status}" />
<TextBox Grid.Row="1" Grid.Column="1" TextWrapping="Wrap" Text="{Binding Description}" />
</Grid>
</UserControl>
At run time a row is selected in your DataGrid, you would have to load the correct user control with some code like this:
myContent.Content = new CustomUserControl();
Each of your custom controls would have to use star sizing, etc. to get the layouts to look right - which is what you were after with your question. Obviously there is still a lot of wireup that would need to be done.
That should give you what you are after. Sorry for the run-around on the first answer!

WPF: ListBox itemtemplate tag navigation through items

I have a listbox that has a datatemplate which contains multiple text boxes. I want the user to be able to tab through all of the textboxes and then to the textboxes of the next list item without having to use CTRL+TAB.
Some XAML:
<DataTemplate x:Key="UsersDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0" Grid.Row="0">
<Label Content="Full Name" />
<TextBox Text="{Binding Path=FullName}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="0">
<Label Content="Address" />
<TextBox Text="{Binding Path=Address}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="1">
<Label Content="City" />
<TextBox Text="{Binding Path=City}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0">
<Label Content="State" />
<TextBox Text="{Binding Path=State}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="1">
<Label Content="Zip" />
<TextBox Text="{Binding Path=Zip}" />
</StackPanel>
</Grid>
</DataTemplate>
<ListBox ItemTemplate="{DynamicResource UsersDataTemplate}"
ItemsSource="{Binding ElementName=MyUserControl, Path=Users}"
Width="914"
Margin="2,2,2,2" />
The idea is that the user may be presented with anywhere from 1 to 10 users in this listbox and they want to be able to tab into the listbox, editing/updating names & addresses and continue tabbing through all 10 users. The problem I am having is that when the user gets to the last textbox (zip) and hits tab, focus leaves the listbox completely.
I know this works with CTRL+TAB but this is unacceptable for the users experience. Is there a way to make the list box tab through its items with the TAB key instead of the CTRL+TAB key?
I tried using variations of KeyboardNavigation.TabNavigation, .ControlNavigation, etc without any luck, though I may be doing something wrong.
Any thoughts?
Hello I tested adding the following code to listbox declaration
<ListBox
KeyboardNavigation.TabNavigation="Continue"
works like a charm ;D

Resources