Binding DataTemplates (or another approach) - wpf

I'm having some troubles trying to dynamically generate content in WPF and after it bind data.
I have the following scenario:
TabControl
- Dynamically generated TabItems through DataTemplate
- inside TabItems, I have dynamic content generated by DataTemplate that I wish to bind (ListBox).
The code follows:
::TabControl
<TabControl Height="252" HorizontalAlignment="Left" Name="tabControl1" VerticalAlignment="Top" Width="458" Margin="12,12,12,12" ContentTemplate="{StaticResource tabItemContent}"></TabControl>
::The Template for TabControl to generate TabItems
<DataTemplate x:Key="tabItemContent">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox ItemTemplate="{StaticResource listBoxContent}" ItemsSource="{Binding}">
</ListBox>
</Grid>
</DataTemplate>
::The template for ListBox Inside each TabItem
<DataTemplate x:Key="listBoxContent">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="22"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding Path=PluginIcon}" />
<TextBlock Grid.Column="1" Text="{Binding Path=Text}" />
</Grid>
</DataTemplate>
So, when I try to do this on code inside a loop to create the tabitems:
TabItem tabitem = tabControl1.Items[catIndex] as TabItem;
tabitem.DataContext = plugins.ToList();
where 'plugins' is an Enumerable
The ListBox is not bounded.
I tried also to find the ListBox inside the TabItem to set the ItemSource property but no success at all.
How do I do that?

The template for TabControl uses a ContentPresenter to presents the SelectedContent, like this:
<ContentPresenter Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
A ContentPresenter's job in life is to expand a DataTemplate. As it does so it sets the DataContext of the constructed visual tree to its Content property, which in this case is bound to SelectedContent.
The SelectedContent is set from the TabItem's Content property, not its DataContext. So setting the DataContext on the TabItem doesn't set the DataContext on the content area's visual tree.
What you want is:
tabItem.Content = plugins.ToList();

Related

Select a Control alone inside a ListView not the whole ListViewItem

I have a ListView with ItemTemplate contains an Image, TextBox, ComboBox, TextBlock etc when selecting any control should highlight the selected control not the whole ListViewItem any style would be fine.
XAML
<ListView Margin="10" ItemsSource="{Binding TCollection}" SelectedItem="{Binding SelectedTImage}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="150"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical">
<Image Width="70" Height="70" HorizontalAlignment="Center">
<Image.Source>
<BitmapImage DecodePixelWidth="300" DecodePixelHeight="300" UriSource="{Binding TImage}"/>
</Image.Source>
</Image>
<TextBox Text="{Binding text}"/>
<TextBlock Text="Contents"/>
<ComboBox/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Any help
What you want is to disable event bubbling. Sure, it hits the control first, but the click event bubbles to the list item, too. In order to stop it, you have to put an event handler on all the controls (button, etc) and set e.Handled = true in the handler to prevent it from going to the ListViewItem.
See: disable event-bubbling c# wpf for more info.
If you never want items to be selected at all (hard to tell with your example), then you'd want to use an ItemsControl instead. That will let you bind to a collection of items and display all of them but it doesn't have the concept of a selected item.

Sharing column width between grids defined in a template

I am trying to layout a couple of controls:
The light gray vertical lines are grid splitters. Each line in this table is a grid with three columns, the label on the left, the splitter in the middle and the control on the right, including the text boxes and the image.
I want all columns to be aligned. Here is the data template I use for each line:
<DataTemplate x:Key="PropertyLineTemplate">
<Grid Margin="0,0,0,1">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Name"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition SharedSizeGroup="Value"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding Title}" />
<GridSplitter Grid.Column="1" Width="2"/>
<SomeUserControl
Grid.Column="2"
Content="{Binding}"
Margin="4, 0, 0, 0"/>
</Grid>
</DataTemplate>
and the template for the view itself:
<DataTemplate DataType="{x:Type vm:PropertiesViewModel}">
<DockPanel>
<!-- Properties -->
<ScrollViewer>
<StackPanel Margin="10">
<ItemsControl ItemsSource="{Binding Properties}"
ItemTemplate="{StaticResource PropertyLineTemplate}" />
</StackPanel>
</ScrollViewer>
</DockPanel>
</DataTemplate>
and the usage of both:
<ContentControl Content="{Binding SelectedItem}" Grid.IsSharedSizeScope="True">
</ContentControl>
where SelectedItem is of type PropertiesViewModel.
I also tried to place Grid.IsSharedSizeScope="True" in each individual grid directly and on the panels containing the control, without effects. How can I achieve that all columns share the width?
Grid.IsSharedSizeScope should be set on the first parent ItemsControl of a Grid, in your case it would be the ItemsControl in the ViewModel template. Then the columns width will be adjusted.
However, if you move one GridSplitter, then it will only adjust that row, not the others (See WPF SharedSizeGroup GridSplitter Issue for a potential solution, if you need resizing.)
Other stuff: you don't need the Stackpanel wrapper around the ItemsControl, since the ItemsControl is the only child. If you need to adjust the panel the ItemsControl uses, then set it on the Property ItemsPanel, like this:
<ItemsControl ItemsSource="{Binding Properties}"
Grid.IsSharedSizeScope="True"
ItemTemplate="{StaticResource PropertyLineTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/> <!-- Or <WrapPanel/> or <UniformGrid/> etc. -->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
However, the Stackpanel is the default panel of the ItemsControl.
Based on Nautious' answer I've solved the problem by defining two ColumnDefinitions as resources and binding the Width property of all columns in question to the Width property of these 'master column definitions':
<ColumnDefinition x:Key="MasterColumnDefinition" />
<ColumnDefinition x:Key="MasterColumnDefinition2" />
<DataTemplate x:Key="PropertyLineTemplate">
<Grid Margin="0,0,0,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Source={StaticResource MasterColumnDefinition}, Path=Width, Mode=TwoWay}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource MasterColumnDefinition2}, Path=Width, Mode=TwoWay}"/>
</Grid.ColumnDefinitions>
...
</Grid>
</DataTemplate>
Might be a little hacky, but it's simple and works. Not, however, that the shared size scope of this solution is the scope in which the resources are defined. I.e. placing these template in the app's resources will lead to all columns in the application being resized synchronously.

Binding the combobox values for an empty datatemplate of a bound itemscontrol wpf

I have an itemscontrol that is bound to a datasource via LINQ. I am trying to create a repeatable control that a) displays the records returned from the LINQ query and b) allows the user to add a new row.
I have 2 collections:
a) fareItemCollection this contains a collection of fareItem objects which include a fareDate and PermitNumber.
b) permitNumbers this is an Ienumerable
I can display the fare items in a repeatable itemsControl. I want to be able to show the permit number as a drop down list (i.e. combobox) of permitNumbers with the permitNumber for that fare selected. The user should be able to select a different permit number to assign to that fareItem if they wish.
I have no problem with retrieving the required data via LINQ, it is how I bind the permitNumbers data in the combobox that I am struggling with. I understand how to bind a dataset to a combobox normally but not when it is within an items control bound to another source. Is this even possible or is there another way to approach this?
Here's my xaml so far - Note PermitNumbers is the name of my iEnumerable which is a public property within the window's class:
<ItemsControl Name="fareItemsControl" ItemsSource="{Binding}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width="160" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Column="0" Grid.Row="0">
<TextBlock>Date</TextBlock>
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="0">
<TextBlock>Driver</TextBlock>
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="1">
<TextBlock Text="{Binding FareDate, StringFormat={}\{0:dd/MM/yy\}, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource FormFieldTextBoxStyle}">
</TextBlock>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="1">
<ComboBox ItemsSource="{Binding Path=PermitNumbers, UpdateSourceTrigger=PropertyChanged}" IsEditable="True"></ComboBox>
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Style.Triggers>
<Trigger Property="HasItems" Value="false">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width="160" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Column="0" Grid.Row="0">
<TextBlock>Date</TextBlock>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="0">
<TextBlock>Driver</TextBlock>
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="1">
<DatePicker SelectedDate="{Binding FareDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource datePickerStyle}"> </DatePicker>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="1">
<ComboBox ItemsSource="{Binding Path=PermitNumbers, UpdateSourceTrigger=PropertyChanged}" IsEditable="True"></ComboBox>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
</ItemsControl>
Thanks
Kay
Further information:
The following is the fareObject that I am using within my fareObjectsCollection that is bound to the fareItemsControl above:
public class FareProxy
{
DateTime _fareDate;
public DateTime FareDate
{
get
{
return _fareDate;
}
set
{
_fareDate = value;
}
}
IEnumerable<string> _permitNumbers;
public IEnumerable<string> PermitNumbers
{
get
{
return _permitNumbers;
}
set
{
_permitNumbers = value;
}
}
}
For each fare item the date displays correctly within the items control but the permitNumbers won't bind to the permitNumbers combobox. I tried to create a permitNumbers property within the class of the window I am using and load the permitNumbers by setting the source path of the comboBox to the permitNumbers property as suggested in the post below but this didn't work either.
You need to make a few changes. First things first... whichever Window or UserControl that your fareItemsControl ItemsControl is in has an object set as its DataContext... I think that you've just set the fareItemCollection as the DataContext. That's your first mistake.
That collection property is in some class. Move your permitNumbers collection as a property to that same class. Now set an instance of that class (that contains both collection properties) as the DataContext.
Now, you'll need to update your ItemsControl.ItemsSource property:
<ItemsControl Name="fareItemsControl" ItemsSource="{Binding fareItemCollection}" ... />
For the final part, #ethicallogics was correct to point out that you'd need to use a RelativeSource Binding to access your permitNumbers collection from inside a DataTemplate. However, he omitted the essential DataContext part. So the correct RelativeSource Path should be something more like this:
<ComboBox ItemsSource="{Binding DataContext.permitNumbers, RelativeSource={
RelativeSource AncestorType={x:Type Window}}}" IsEditable="True"></ComboBox>
This means that the ComboBox.ItemsSource is looking for a property named permitNumbers in the object that is set as the DataContext for the parent Window. That should do the trick.

Stretching the items of an ItemsControl

I'm bulding a small WPF app over here. It's all built strictly with MVVM, using nothing but DataTemplates linked to view model types.
I've seen alot of questions about how to stretch and clip the contents of ListBoxes so that the items fill its parent. After alot of experimenting I managed to get my head around that but now I find myself in the same scenario with the ItemsControl but the same tricks doesn't seem to work.
Here's one of the DataTemplates in use (a simple TextBox). Note how I tried setting the HorizontalAlignment ...
<DataTemplate DataType="{x:Type vm:OneOfMyViewModelTypes}">
<TextBox
Text="{Binding Path=Value}"
HorizontalAlignment="Stretch"
/>
</DataTemplate>
Here's the ItemsControl inside a Grid ...
<Grid Background="Gray">
<Grid.Margin>
<Thickness
Left="{StaticResource ConfigurationDefaultMargin}"
Right="{StaticResource ConfigurationDefaultMargin}"
Bottom="{StaticResource ConfigurationDefaultMargin}"
Top="{StaticResource ConfigurationDefaultMargin}"
/>
</Grid.Margin>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="_key" Width="Auto"/>
<ColumnDefinition SharedSizeGroup="_value" Width="*"/>
</Grid.ColumnDefinitions>
<ItemsControl
Background="DimGray"
Grid.IsSharedSizeScope="True"
ItemsSource="{Binding Path=Configuration, Mode=OneWay}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="_key"/>
<ColumnDefinition SharedSizeGroup="_value"/>
</Grid.ColumnDefinitions>
<TextBlock
Style="{StaticResource ExtensionConfigurationLabel}"
Grid.Column="0"
Margin="5,5,5,0"
Text="{Binding Path=Caption}"
/>
<ContentPresenter
Grid.Column="1"
HorizontalAlignment="Stretch"
Margin="5,5,5,0"
Content="{Binding}"
/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
I've used colors to see how the controls sizes. The Grid is gray and the ItemsControl is dark gray.
This is the result ...
As you can see from the colors the containing Grid stretches while the ItemsControl does not. I did set its HorizontalAlignment property to Stretch but it seems it has no effect. Is there anything else I need do?
Thanks
You have two columns in your main (outer) grid, yet you use only the first column.
The second column uses all the remaining space.

WPF Element Binding With Dynamically Generated UI Elements

I have a page in which I have a grid with 2 columns, one accommodating 80* width and the other accommodating 20*. Beneath the grid is a stack panel to which I load UI elements (child stack panels) at runtime.
My objective is to bind the widths of the stackpanels to the columns of the grid. This works perfectly and the stackpenels resize if the dynamic content is mocked to be static in the design view. But when the application is run, the bindings fail and the widths are not bound.
Is there any way to refresh the bindings so that I can notify the stackpanels to refer the widths of the grid and configure its's size?
Updated:
This is what my XAML looks like:
<Page x:Class="WPFTestApp.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid x:Name="resizeGrid"
Grid.Column="0" Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="leftColumn"
Width="80*" />
<ColumnDefinition x:Name="rightColumn"
Width="20*" />
</Grid.ColumnDefinitions>
</Grid>
<StackPanel x:Name="contentPanel"
Grid.Column="0" Grid.Row="0" >
<!-- Dynamic StackPanels are added here -->
</StackPanel>
</Grid>
</Page>
In my code, I create StackPanel elements and add it to the contentPanel by saying:
contentPanel.Children.Add(...);
Unfortunately, I HAVE to use StackPanels here. :-(
The markup of a dynamically created StackPanel element is as follows (note that I use a Binding to the grid already in the XAML):
<StackPanel x:Name="element01"
Orientation="Horizontal"
Width="{Binding ElementName=resizeGrid, Path=ActualWidth}" >
<StackPanel x:Name="leftPanel"
Orientation="Vertical"
Width="{Binding ElementName=leftColumn, Path=ActualWidth}">
<!-- Content goes here -->
</StackPanel>
<StackPanel x:Name="rightPanel"
Orientation="Vertical"
Width="{Binding ElementName=rightColumn, Path=ActualWidth}">
<!-- Content goes here -->
</StackPanel>
</StackPanel>
The XAML code for my dynamic StackPanel elements are generated through an XSLT transformation
Note the xmlns:x namespace dynamic StackPanel. This also has to be done because of it being generated through XSLT
As far as I know setting the Width of a StackPanel with Orientation="Horizontal" will have no effect. The size of a StackPanel does not exceed the size of its content in its oriented direction.
Why do you "HAVE" to use StackPanels here? You almost certainly want DockPanels, given your objective. You won't have to bind or set anything, because the DockPanel will fill the cell automatically.
Try binding the ActualWidth instead of Width.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid x:Name="resizeGrid"
Grid.Column="0" Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="leftColumn"
Width="{Binding ElementName=contentPanel, Path=ActualWidth}" />
<ColumnDefinition x:Name="rightColumn"
Width="*" />
</Grid.ColumnDefinitions>
</Grid>
<StackPanel x:Name="contentPanel"
Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" >
<StackPanel Background="Yellow" Width="100" Height="25" />
<StackPanel Background="Green" Width="120" Height="25" />
<StackPanel Background="Red" Width="140" Height="25" />
<TextBlock Background="Gray" Text="{Binding ElementName=contentPanel, Path=ActualWidth}" />
</StackPanel>
</Grid>
The following changes I have done above:
1) Set the leftColumn to the ActualWidth of the contentPanel
2) Set the rightColumn to *
3) contentPanel HorizontalAlignment should be changed other then Stretch. Here I have changed it to Left
HTH
I found a solution for this issue. (Sorry for the late response)
What I did was, I cut and pasted the "resizeGrid" Grid control into the "contentPanel" Stackpanel.
As I mentioned above, the contents within the contentPanel are generated through an XSLT transformation and this surely creates a namespace reference issue. I noticed "element not found..." message in the Output window whenever this Window is loaded - this proved me right.
Inserting the "resizeGrid" into the "contentPanel" helped because then both the controls are within the same namespace and can be referenced for a PropertyBinding using "ElementName" attribute.
Thank you for your support! :-)

Resources