WPF: DataTemplate binding for different usercontrol - c#

I'm new on WPF and I'm tryng to figure out how to implement a binding with different type of user control.
After the user clicks a button, a usercontrol (a simple shape like rectangle or ellipse) is added to the window.
I'm tryng to use an MVVM approach so the xaml appears as follow:
...
Title="{Binding Path=Titolo}"
Height="450" Width="800"
d:DataContext="{d:DesignInstance vm:MainWindowViewModel}">
<Canvas>
<Button Content="Add Shape" Command="{Binding TestCommand}"/>
<ItemsControl ItemsSource="{Binding RectCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<uc:MyCustomRectangle/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding xLT}" />
<Setter Property="Canvas.Top" Value="{Binding yLT}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Canvas>
All works fine but I want to add different type of UserControl (not only MyCustomRectangle) using the same button (for example, randomly adding rectangle or ellipse).
A possible solution could be duplicate the section of ItemsControl and select a different collection of binding:
<Canvas>
<Button Content="Add Shape" Command="{Binding TestCommand}"/>
<ItemsControl ItemsSource="{Binding RectCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<uc:MyCustomRectangle/> <!-- bind to my usercontrol -->
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding xLT}" />
<Setter Property="Canvas.Top" Value="{Binding yLT}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
<ItemsControl ItemsSource="{Binding EllipseCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<uc:MyCustomEllipse/> <!-- bind to my usercontrol -->
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding xLT}" />
<Setter Property="Canvas.Top" Value="{Binding yLT}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Canvas>
I don't think this is the correct solution, especially because I would to add many different types of shapes (and also text and images).
So, if exist, what is the correct way to bind a data template to different type of usercontrol?
Is MVVM the correct approach to solve this problem?

What you can do is to bind to a global list with all Shapes.
And then you can define different DataTemplates for different Types.
Like this:
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type MyType1}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type MyType2}">
....
</DataTemplate>
</ItemsControl.Resources>

Related

How to force WPF textboxes contents to be displayed above other controls similar to excel cells

I have an ItemsControl that contains a textbox for each binding item and I want to allow overlaying text box content if its content is wider than the textbox width similar to excel cells overlaying behavior.
Is there a way to do this?
<ItemsControl ItemsSource="{Binding Path=MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Width="100"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}" TextWrapping="WrapWithOverflow"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can use tooltip for that. You just need to bind the source with itself.
<ListView ItemsSource="{Binding Path=MyStringCollection}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Width="100"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}" TextWrapping="WrapWithOverflow" ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Text}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
For further information:
DrawToolTip Event
Set ToolTipSize
To replicate a similar behavior, you can make use of a Popup.
To implement a similar behavior, you first must disable content wrapping of your TextBox.
Then replace the current TextBox with a TextBlock, which is used to display the text.
The Popup, which actually contains the editable TextBox, will then overlay this TextBlock at the exact position, thus hiding the TextBlock to make it appear to stretch and overlay the adjacent items.
A MultiTrigger will close the Popup as soon as the focus moved outside the ListBoxItem.
To improve performance you should use the VirtualizingStackPanel as items host.
<ListView ItemsSource="{Binding MyStringCollection}"
HorizontalAlignment="Left"
Width="800">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Border BorderThickness="1"
BorderBrush="Gray">
<TextBlock Text="{Binding}" />
</Border>
<Popup x:Name="EditableTextSiteHost"
PlacementTarget="{Binding ElementName=TextSite}"
Placement="Relative"
Height="{Binding ElementName=TextSite, Path=ActualHeight}"
AllowsTransparency="True"
FocusManager.FocusedElement="{Binding ElementName=EditableTextSite}">
<TextBox x:Name="EditableTextSite"
Text="{Binding TextData}" />
</Popup>
</Grid>
<DataTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=IsSelected}"
Value="True" />
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=IsKeyboardFocusWithin}"
Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="EditableTextSiteHost"
Property="IsOpen"
Value="True" />
</MultiDataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<!-- Make items overlap -->
<Setter Property="Margin"
Value="-2,0,0,0" />
<Setter Property="Padding"
Value="0" />
<Setter Property="Width"
Value="50" />
<Style.Triggers>
<!-- Apply zero Margin on the first item -->
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}"
Value="{x:Null}">
<Setter Property="Margin"
Value="0,0,0,0" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
To-do
This is just a raw example, a proof of concept. You would have to improve the behavior. For example, you would want to close the Popup when the user scrolls or moves the Window. Otherwise, since Popup itself is a Window, the Popup would not move to follow the placement target. You could move the related logic to an attached behavior.
You likely also want to improve the selection behavior. Currently the selection highlight border does not (virtually) extend to surround the Popup. You have to mimic this by applying a Border on the TextBox that will replicate the ListBoxItem highlight border.
I managed to produce the excel cells overlay behavior by using a Grid with dynamic column count using this helper dependency properties https://rachel53461.wordpress.com/2011/09/17/wpf-grids-rowcolumn-count-properties/ as a container template of ItemsControl and binding the column index of each textbox to the ordered item index and binding Grid.ZIndex to the reversed index to be displayed above the adjacent text boxes.
<ItemsControl ItemsSource="{Binding MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid HorizontalAlignment="Left" helpers:GridHelpers.ColumnCount="{Binding MyCollection.Count}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding ItemIndex}"/>
<Setter Property="Grid.ZIndex" Value="{Binding ReversedIndex}" />
<Setter Property="Grid.ColumnSpan" Value="{Binding MaxMergedCells}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBox MinWidth="30" Text="{Binding }"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>

WPF TemplateSelector to draw different figures on just one canvas

I have a block of code in my xaml that it is repeated several times, each to draw a different predefined figure (rectangle 1,2...n, sellipse1,2...n, etc) programmatically:
<ItemsControl ItemsSource="{Binding MyEllipse}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="200" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="{Binding Width}" Height="{Binding Height}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I would like to find a way of having this summarized with a Data/Item TemplateSelector, but after trying several approaches i couldn't get it to work.
Any little working example would be much appreciated, thanks!

Unable to bind item property in itemcontainerstyle

I created a .NET WPF Application with MvvmLight this evening. I'm trying to show a couple of circles on my main window.
My MainWindow.xaml is bound to MainViewModel.cs. MainViewModel contains an ObservableCollection with SpaceObjects. I'm trying to show this collection using itemcontrols.
<ItemsControl ItemsSource="{Binding SpaceObjects}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding }"></Setter>
<Setter Property="Canvas.Top" Value="{Binding Radius}"></Setter>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="{Binding Radius}" Height="{Binding Radius}" Fill="Blue" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In the ItemTemplate I am able to bind to a SpaceObject property (like Radius). In ItemContainerStyle I can only choose from the context:
I would like to choose from the items in the source (ItemsScourse SpaceObjects), so I can bind the X and Y coordinates from the circle. What am I doing wrong?
<DataGrid.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<d:Style.DataContext>
<x:Type Type="SpaceObject" />
</d:Style.DataContext>
<Setter Property="Canvas.Left" Value="{Binding }"/>
<Setter Property="Canvas.Top" Value="{Binding Radius}"/>
</Style>
</DataGrid.ItemContainerStyle>

How to set style for ItemsPanel from outside?

I define a style to make all StackPanel green:
<Window.Resources>
<Style TargetType="StackPanel">
<Setter Property="Background" Value="Green" />
</Style>
</Window.Resources>
But if I use StackPanel as panel template then it's NOT green:
<UniformGrid>
<StackPanel /><!-- this one is green -->
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel /><!-- this one is not -->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</UniformGrid>
Why? How to make it also green?
Either move the implicit Style to App.xaml or add resource that is based on the implicit Style to the ItemsPanelTemplate:
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<ItemsPanelTemplate.Resources>
<Style TargetType="StackPanel" BasedOn="{StaticResource {x:Type StackPanel}}" />
</ItemsPanelTemplate.Resources>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Types that don't inherit from Control won't pick up implicit styles if you don't do any of this.

ItemsControl with none background WPF

I would like to ask you, if is possible to have ItemsControl without background (x:null), not transparent.
I have collection with data, and these are showed in ItemsControl with help of DataTemplate. Some data in datatemplate are collapsed, and I need to be able to clickable on another control behind the itemscontrol.
Here is example what I mean:
<Button x:Name="bt_behind"></Button>
<ItemsControl ItemsSource="{Binding ListOfData}" Background="{x:Null}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Background="{x:Null}"></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type Class:Data}">
<Grid Width="100" Background="{x:Null}">
<Rectangle x:Name="rec" Fill="Red" Height="100" Visibility="Collapsed">
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="true">
<Setter TargeName="rec" Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Example where item3 is partly collapsed and marked area, where is empty place
I set everywhere the background to null (try itemcontainerstyle too), but without success. On button behind ItemsControl still is not clickable. I think that ItemsControl have transparent background for events, but is it possible to remove this background?
Thanks for any advice and sorry for my english:)
-pav-
Well, as i said, it's all working. Fixed XAML:
<Grid>
<Button x:Name="bt_behind" Content="behind" Click="Bt_behind_OnClick"/>
<ItemsControl ItemsSource="{Binding ListOfData}" Background="{x:Null}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Background="{x:Null}"></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Data}">
<Grid Width="100" Background="{x:Null}">
<Rectangle x:Name="rec" Fill="Red" Height="100" Visibility="Collapsed"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="true">
<Setter TargetName="rec" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Just for test.
private void Bt_behind_OnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show("");
}

Categories