How to refine the DataContext locally for a Grid? - wpf

I can't find the way to move the reference to property P1 at Grid level.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding P1.Name}"/>
<TextBox Grid.Row="1" Text="{Binding P1.Age}"/>
</Grid>
P1 is a property of the View-Model instance set as DataContext for the Window. It references a Person instance with Name and Age properties. Is there a possibility to "refine" this DataContext for the Grid, so that cells can be bound like:
<Grid DataContext="P1">
<TextBox Grid.Row="0" Text="{Binding Name}"/>
<TextBox Grid.Row="1" Text="{Binding Age}"/>
</Grid>
I'm trying to do this so that different Grid can be bound to different Person (P1, P2, ...).

As kindly suggested by #ASh, the correct syntax is to use a Binding object to set the DataContext. If two Person instances P1 and P2 where to be shown side-by-side, this code would work:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- Binding first grid DataContext to P1 -->
<Grid Grid.Column="0" DataContext="{Binding P1}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding Name}"/>
<TextBox Grid.Row="1" Text="{Binding Age}"/>
</Grid>
<!-- Binding second grid DataContext to P2 -->
<Grid Grid.Column="1" DataContext="{Binding P2}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding Name}"/>
<TextBox Grid.Row="1" Text="{Binding Age}"/>
</Grid>
</Grid>

Related

How do I set the ListView height to the height of the contining row in XAML

I can't figure out the syntax to tell the ListView its height is the height of the row that contains the ListView, that is, the ListView's Parent.
This is the xaml in the user control. I have put what I thought would work, but doesn't. I left it there so you would see what I am attempting.
<ListView Grid.Row="1"
Height="{Binding Path=Height,RelativeSource={RelativeSource AncestorType=Grid.Row}}"
ItemsSource="{Binding Folders}"
ItemTemplate="{StaticResource FolderListTemplate}"
SelectionMode="Single"
SelectedIndex="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Margin="3"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.CanContentScroll="True"
/>
Thanks,
Just set the RowDefinition height to Auto, here is my xaml:
<Grid Background="DimGray">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<StackPanel Background="CornflowerBlue"/>
<ListView Grid.Row="1"
x:Name="ListView"
ItemsSource="{Binding Items}"
SelectionMode="Single"
SelectedIndex="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Margin="3"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.CanContentScroll="True"/>
<StackPanel Grid.Row="2" Background="CornflowerBlue"/>
</Grid>
And the output:
If you use star notation then the row it is goin to take the available height for the star value you gave, in this case 1* which is the same as *****.
If I modify to use star value as you:
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
The output:
Hope this helps

Set focus to a specific element in a template with the item is selected

Have a ListView with a template selector
What I need is for the TextBox named tbStringSVfieldValue to get focus when this ListBoxItem gets selected (clicked or arrow) ?
<DataTemplate x:Key="fieldTemplateStringSV">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4" Text="Enter key to save. Tab to get the first match from existing values. Value is only committed on Enter key." VerticalAlignment="Center" Style="{StaticResource Italic}"/>
<ScrollViewer Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" VerticalContentAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<TextBox x:Name="tbStringSVfieldValue" BorderBrush="SteelBlue" BorderThickness="2" TextWrapping="Wrap"
Text="{Binding Path=FieldValue, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource stringTabConverter}}"
Validation.Error="Validataion_Error" AcceptsReturn="True" AcceptsTab="True"
PreviewKeyDown="tbStringSVfieldValue_PreviewKeyDown"/>
<!--, Converter={StaticResource stringTabConverter}-->
</ScrollViewer>
<TextBlock Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" Text="Existing values. Click to populate the value above." VerticalAlignment="Center" Style="{StaticResource Italic}"/>
<ListBox Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="4" Height="150" Margin="5,2,0,0" x:Name="lbSVtextPast" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.IsDeferredScrollingEnabled="True"
FontStyle="Normal"
ItemsSource="{Binding Path=PastEntries}" SelectedItem="{Binding Path=SelectedPastEntry}"
SelectionChanged="lbSVtextPastSelectionChanged"/>
</Grid>
</DataTemplate>
above I tried
<Grid FocusManager.FocusedElement="{Binding ElementName=svTBsv}">
<ScrollViewer x:Name="svTBsv" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" VerticalContentAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
FocusManager.FocusedElement="{Binding ElementName=tbStringSVfieldValue}">
<TextBox x:Name="tbStringSVfieldValue" BorderBrush="SteelBlue"
note below does NOT work
this.tbStringSVfieldValueStringSV is null
it only exists in the template
need to somehow get access to the correct template
note there will be more than one DataTemplate x:Key="fieldTemplateStringSV" in the ListBox
private void lbCurDocFields_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show("lbCurDocFields_SelectionChanged " + sender.GetType().ToString());
if (sender is ListBox)
{
ListBox lb = (ListBox)sender;
//lb.Template.VisualTree;
MessageBox.Show(lb.Template.ToString());
if (lb.SelectedItem != null)
{
MessageBox.Show(lb.SelectedItem.GetType().ToString());
if (lb.SelectedItem is GabeLib.DocFieldStringSV)
{
if (this.tbStringSVfieldValueStringSV != null)
this.tbStringSVfieldValueStringSV.Focus();
}

How to make the vertical scrollviewer auto display in groupbox

below is my xaml structure:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text = "{Binding Test}" />
<GroupBox Header = "Test" Grid.Row="1">
<TextBlock Text = "{Binding Description}" />
</GroupBox>
</Grid>
My Description is very long, but there's no vertical scrollviewer display in my groupbox. I don't want to set a specific height to the groupbox. Anyone can help?
You can set TextWrapping="Wrap" and wrap it in ScrollViewer with HorizontalScrollBarVisibility="Disabled":
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Test}" />
<GroupBox Header="Test" Grid.Row="1">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<TextBlock TextWrapping="Wrap" Text="{Binding Description}" />
</ScrollViewer>
</GroupBox>
</Grid>

How to add new elements to the base Grid from an inherited DataTemplate?

I have two objects: Bicycle and BicycleFullSuspension, which inherits Bicycle and contains additional properties (strings and decimals). I managed to make a functioning DataTemplateSelector that will choose either my LbxItemTemplateBicycle DataTemplate or my LbxItemTemplateFullSuspension DataTemplate, depending on to which class the object in an ObservableCollection belongs. Both of these templates are bound to the parent template ListBoxItemTemplate.
I am trying to display information about each object in a ListBox, and each of the inheriting templates should add a few more fields to the grid in the parent DataTemplate. My problem is that I cannot figure out how to add WPF elements to the grid in ListBoxItemTemplate from one of the inherited templates. I cannot assign a key to the grid in the template, so I am not sure how to specify that additional TextBlocks should end up in the same grid that is in the parent template. (Right now, additional TextBlocks are stacked on top of the parent grid.)
<DataTemplate x:Key="ListBoxItemTemplate">
<Border Name="LbxItemTemplate" BorderBrush="DarkRed" BorderThickness="2" Padding="5" Margin="5">
<Grid>
<Grid.RowDefinitions>
...
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
...
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Bike Year:" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding BikeYear}" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Bike Color: "/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding BikeColor}"/>
...
</Grid>
</Border>
</DataTemplate>
<DataTemplate x:Key="LbxItemTemplateFullSuspension" DataType="{x:Type app:BicycleFullSuspension}">
<Grid>
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource ListBoxItemTemplate}" />
<TextBlock Grid.Row="6" Grid.Column="0" Text="Shock Travel"/>
<TextBlock Grid.Row="6" Grid.Column="1" Text="{Binding ShockTravel}"/>
</Grid>
</DataTemplate>
I found these links helpful for getting to this point:
http://dariosantarelli.wordpress.com/2011/07/28/wpf-inheritance-and-datatemplates/
http://zamjad.wordpress.com/2009/12/31/applying-data-template-conditionally/
Is there a way to use data-template inheritance in WPF?
Edit:
I'm not sure why I didn't think to put the Border on the template inheriting the base, but by nesting a StackPanel inside of the Border in the inheriting template (StackPanel contains the ContentPresenter with the base template content along with the Grid that has the added information), everything lines up very nicely:
Working solution, using input from XAMeLi:
<DataTemplate x:Key="ListBoxItemTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
...
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" SharedSizeGroup="LabelColumnGroup" />
<ColumnDefinition Width="150" SharedSizeGroup="LabelColumnGroup" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Bike Year:" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding BikeYear}" />
...
</Grid>
</DataTemplate>
<DataTemplate x:Key="LbxItemTemplateFullSuspension" DataType="{x:Type app:BicycleFullSuspension}" >
<Border BorderBrush="DarkRed" BorderThickness="2" Padding="5" Margin="5" Grid.IsSharedSizeScope="True" Width="500">
<StackPanel>
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource ListBoxItemTemplate}" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
...
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelColumnGroup" />
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelColumnGroup" />
</Grid.ColumnDefinitions>
...
<TextBlock Grid.Row="7" Grid.Column="0" Text="Shock Type: "/>
<TextBlock Grid.Row="7" Grid.Column="1" Text="{Binding ShockType}"/>
...
</Grid>
</StackPanel>
</Border>
</DataTemplate>
You cannot add UIElemets to a data template, but you can layout the bigger template to look like it has added text to the base template.
Basically, you are on the right path, only the layout of the big template is corrupt. (Too bad you omitted the structure of your rows and columns)
Try this:
<DataTemplate x:Key="ListBoxItemTemplate">
<Border Name="LbxItemTemplate" BorderBrush="DarkRed" BorderThickness="2" Padding="5" Margin="5">
<Grid>
<Grid.RowDefinitions>
...
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
SharedSizeGroup="LabelGroupColumn"/>
...
</Grid.ColumnDefinitions>
....
</Border>
</DataTemplate>
<DataTemplate x:Key="LbxItemTemplateFullSuspension">
<Grid Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/><!--This will hold the default template-->
<!--From here define the same structure as in the default template, if you have rows for margins and such-->
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
SharedSizeGroup="LabelGroupColumn"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource ListBoxItemTemplate}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Shock Travel"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding ShockTravel}"/>
</Grid>
</DataTemplate>
It seems that your grids have Label-Value structures and you want all the values to start from the same vertical line (this is very good from UX point of view). Notice the SharedSizeGroup setters on column definitions and the Grid.IsSharedSizeScope="True" on a mutual parent. This will keep the width of the label column to be synchronized among the grids.

WPF - Bind to selecteditem of listbox between user controls

I have two usercontrols, the first with a listbox that is bound to a list of Customers that displays some simple details for each customer.
The second user control I would like to be a more detailed view of whichever customer is selected in the listbox of the first usercontrol.
Is it possible to set up a binding in the second control to bind to the selected item in the first user control?
My List box:
<ListBox Name="lstCustomer" ItemsSource="{Binding Customers}" >
<ListBox.Resources>
<DataTemplate DataType="{x:Type MyApplication:Customers}">
<Label Grid.Row="0" Content="{Binding Customer.name}" FontSize="14" FontWeight="Bold" Padding="5" />
<Label Grid.Row="1" Grid.Column="0" Content="{Binding Customer.telephone}" Padding="10,5" />
</Grid>
</Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>
Detailed view Usercontrol (So Far)
<Grid x:Name="containingGrid" DataContext="{Binding ElementName=lstCustomers, Path=SelectedItem}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Customer.name}" FontSize="23"/>
</Grid>
Thanks
Greg
I would suggest to have a property in your ViewModel of Customer object say SelectedCustomer and bind it to the SelectedItem of your listbox like this -
<ListBox Name="lstCustomer" ItemsSource="{Binding Customers}"
SelectedItem = "{Binding SelectedCustomer}" >
. . . . .
</ListBox>
Since you mentioned that both user controls are in same view, so i am assuming that they share a same ViewModel. In that case you can simply set the data context this way -
<Grid x:Name="containingGrid" DataContext="{Binding SelectedCustomer}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" FontSize="23"/>
</Grid>
Yes, you can - if you give the listbox a name of CustomerList then you can bind to its SelectedItem property using a binding like "{Binding ElementName=CustomerList, Path=SelectedItem}".

Resources