How can I add a single control instance to an ItemsPanelTemplate? - c#

In my project, I have an ItemsControl which has as its ItemsPanel template a Grid. I would like to add a single control with this grid, completely independent of the ItemsControl 'Items', which can be accessed in the code-behind. What is the best way to do this?
More detail:
I am implementing a keyframe animation timeline in WPF. I do this by having a set of 'Indicators' which is bound to an ItemsControl; the ItemsControl then positions each of these on a Grid according to their position using the Margin property:
<DataTemplate x:Key="keyIndicatorTemplate">
<Border Width="1" Height="1"
BorderBrush="Black" Background="Black" BorderThickness="0"
HorizontalAlignment="Left" VerticalAlignment="Top"
Margin="{Binding Converter={StaticResource ResourceKey=keyIndicatorMarginConv}}"
></Border>
</DataTemplate>
This Grid of indicators then automatically sizes to the key frames, and is zoomed and panned using the ItemsControl LayoutTransform property, and the encompassing ScrollViewer:
<Grid>
<ScrollViewer Name="timelineScrollViewer" Background="LightCyan" HorizontalScrollBarVisibility="Visible">
<ItemsControl Name="KeyGridPresenter" ItemTemplate="{StaticResource ResourceKey=keyIndicatorTemplate}" >
<ItemsControl.LayoutTransform>
<ScaleTransform
ScaleX="{Binding Path=ZoomX, ElementName=TimelineUserControl}"
ScaleY="{Binding Path=ZoomY, ElementName=TimelineUserControl}">
</ScaleTransform>
</ItemsControl.LayoutTransform>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid Name="KeyGrid">
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
I would like to add a semi-transparent 'highlighter/caret' which the user controls with the mouse, but since this design is based on the grid being an abstract 'surface' which the ItemsControl and ScrollViewer provide a 'window' to, I need to add the control in such a way that the transforms of the containers apply to it (i.e. a sibling or child of the grid)
How can I modify my ItemsTemplate, so that it instantiates a single control (border/rectangle/etc), independent of the Items?

You cannot modify the children of a panel being used as the ItemsPanelTemplate
What I usually do instead is put both the ItemsControl and the overlay control in an panel that allows its children to overlap, such as a Grid
For example,
<ScrollViewer Name="timelineScrollViewer" ...>
<Grid>
<ItemsControl Name="KeyGridPresenter" ... >
</ItemsControl>
<Grid x:Name="SomethingDrawnOnTopOfItemsControl">
...
</Grid>
</Grid>
</ScrollViewer>

Related

WrapPanel hides half of the last uiElement of each line

Each UserControl I have in a stackPanel has a WrapPanel, and each UserControl in this WrapPanel is a word (so they have a different size from each other, depending on the length of the word), so that a sentence appears.
Here is an image to help you understand better:
In pink it is the UserControl "sentence" that contain each of them ONE wrapPanel
In Green it is all the UserControl "word" that have all different size according to the word length.
Here is the UserControl "word":
<UserControl x:Class="Coran_seul.UC.UserControl_WordInVerse"
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"
xmlns:local="clr-namespace:Coran_seul.UC"
mc:Ignorable="d" Margin="5,0,5,0">
<StackPanel>
<TextBlock x:Name="textBlock_arabic" Text="ٱلْأَنفَالُ " FontSize="20" HorizontalAlignment="Center" FontFamily="Noto Naskh Arabic" MouseEnter="textBlock_arabic_MouseEnter" MouseLeave="textBlock_arabic_MouseLeave" Cursor="Hand" MouseDown="textBlock_arabic_MouseDown"/>
<TextBlock x:Name="textBlock_french" Text="Les surplus (de bénéfice)" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
And here is the UserControl "sentence" (with the wrapPanel I have a problem with)
<UserControl x:Name="userControl" x:Class="Coran_seul.UC.UserControl_VerseInSurah"
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"
xmlns:local="clr-namespace:Coran_seul.UC"
mc:Ignorable="d" Margin="0,5,0,5"
>
<Border x:Name="Border_Verse" >
<StackPanel Orientation="Horizontal" x:Name="grid">
<Label x:Name="Label_VersetNum" Content="2" Height="{Binding ActualHeight, ElementName=WrapPanel_Mots, Mode=OneWay}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" VerticalAlignment="Stretch" HorizontalAlignment="Left" MouseDown="Label_VersetNum_MouseDown"/>
<WrapPanel x:Name="WrapPanel_Mots" Width="{Binding ActualWidth, ElementName=userControl, Mode=OneWay}" />
</StackPanel>
</Border>
The problem :
Now that I've entered the code, here's the problem I'm having with my wrapPanel:
The last word of each line does not wrap, even if it is larger than the space left (= it is therefore hidden/cut off at the edge of the wrapPanel)
Again I have a video to better explain the problem :
https://streamable.com/lpdf38
I don't know what it's due to and I'm desperately looking since yesterday not to have this problem anymore, knowing that the stackPanel containing all the userControls has a right margin of 20 so that it's not hidden behind the scrollbar.
Please help me to find a solution so that the word as long as it is hidden even by one pixel is wrapped to the next line.
Thank you,
The problem is that you bind the WrapPanel's Width to that of its UserControl parent without subtracting the Width of the Label element.
You should not need to bind any Width at all, when you use suitable Panel elements, like e.g. a Grid or a DockPanel instead of StackPanel.
<Border x:Name="Border_Verse">
<DockPanel>
<Label x:Name="Label_VersetNum" DockPanel.Dock="Left" .../>
<WrapPanel x:Name="WrapPanel_Mots">
</DockPanel>
</Border>
<Border x:Name="Border_Verse">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label x:Name="Label_VersetNum" Grid.Column="0" .../>
<WrapPanel x:Name="WrapPanel_Mots" Grid.Column="1">
</DockPanel>
</Border>
You may also consider to use an ItemsControl to display a sentence. It would use a WrapPanel as its ItemsPanel and the word UserControl in its ItemTemplate:
<ItemsControl ItemsSource="{Binding Words}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<uc:UserControl_VerseInSurah/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
where Words is a collection of word data item objects in the view model, i.e. the object in the DataContext of the view. The two TextBlocks in the word UserControl would have their Text property bound to an appropriate property on the word data item class.
You may not even need the word UserControl at all, since you can simply move the elements from its XAML into the DataTemplate that is used as ItemTemplate.
See Data Templating Overview for details.

WPF UniformGrid stretch the text of buttons equal in every item

I am struggling with a ListView of many Buttons - I want to stretch the text of all buttons to the same size. I saw this answer for TextBlocks, but I need a solution for Buttons. I broke my problem down to this minimal example.
This is my template. I am stretching the Text of my Button with a ViewBox.
<DataTemplate x:Key="MyTemplate">
<Button>
<Button.Template>
<ControlTemplate TargetType="Button">
<Border BorderThickness="3" BorderBrush="Black" Margin="1,1,1,1">
<Viewbox Stretch="Uniform">
<TextBlock Text="{Binding Name}"/>
</Viewbox>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
I am using the template in a ItemsControl with a UniformGrid like this:
<ItemsControl ItemsSource="{Binding MyItems}" ItemTemplate="{StaticResource MyTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="4"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The result of this combination is like this. Every Button is stretching the text different.
But I want to stretch the Text of every button to the same FontSize. So the FontSize of my Buttons sould be the largest possible FontSize of all Buttons.
The result should look like this:
Is there a way to reach this? I appreciate any help!

How can I layer a Polygon over a StackPanel, which is inside a ScrollViewer?

I have a horizontal ScrollViewer that contains a single StackPanel, which is initially empty. UserControls are created on button-click, and added to the StackPanel. The code looks something like this:
<ScrollViewer Grid.Column="1" Grid.Row="0" RequestedTheme="Dark" ScrollViewer.HorizontalScrollMode="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollMode="Disabled" ScrollViewer.VerticalScrollBarVisibility="Hidden">
<StackPanel x:Name="Container" HorizontalAlignment="Left" VerticalAlignment="Top" Width="Auto">
<!--User controls added here dynamically-->
</StackPanel>
</ScrollViewer>
As you can see the ScrollViewer is itself in a larger Grid. Now I would like a Polygon to be added to this ScrollViewer, but I cannot add it as a child directly, since "Content can only be set once". If I add it to the StackPanel in code-behind (after setting Canvas left, top and ZIndex), the UserControls are added below the Polygon. I want them to be under or over, either will do. Is this even possible? Right now, my Polygon shares the same Grid Row and Column as the ScrollViewer. Here is my Polygon:
<Polygon x:Name="Gripper" Grid.Column="1" Grid.Row="0" Points="0,0 0,730 -30,750 -30,800 30,800 30,750 0,730 0,100" Stroke="#DEDEDE" StrokeThickness="1" Opacity="1.0" ManipulationMode="TranslateX" ManipulationDelta="Gripper_ManipulationDelta">
<Polygon.Fill>
<SolidColorBrush Color="#FFFFFF" Opacity="0.9"/>
</Polygon.Fill>
<Polygon.RenderTransform>
<CompositeTransform />
</Polygon.RenderTransform>
</Polygon>
I am okay with adding it in XAML or C#, but I think I would prefer to do it in C# since I would like to change the points based on the size of the screen.
Put another container control between the StackPanel and the Polygon. An intermediate Grid will let you control their placement by row and column as normal. An intermediate StackPanel will let the Container stack and Polygon stack separately from the items within the Container stack:
<ScrollViewer>
<Grid>
<StackPanel x:Name="Container" HorizontalAlignment="Left" VerticalAlignment="Top" Width="Auto">
<!--User controls added here dynamically-->
</StackPanel>
<Polygon />
</Grid>
</ScrollViewer>

How to create a DataTemplate with a StackPanel of a variable number of StackPanels

I am trying to create a DataTamplate which should contain a StackPanel with a certain number of StackPanels.
<DataTemplate x:Key="DataTemplate">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Rectangle Fill="Aqua" Margin="2" Height="100" Width="50" VerticalAlignment="Top" HorizontalAlignment="Left"/>
</StackPanel>
</StackPanel>
</DataTemplate>
The code snippet above is just for a better understanding of my desired result, as the elements in the imbricated StackPanel will be binded.
This generates the following error message:
VisualTree of ItemsPanelTemplate must be a single element.
Any alternatives that could work?
You should ItemsControl with ItemsSource bound to your source list. In ItemsControl.ItemsPanel you can set which panel you want to use for items. In your case you should use StackPanel with Orientation=Vertical as ItemsPanel. See first sample here. But vertical StackPanel is already default ItemsPanel so you can omit it.
Inner StackPanel should be specified as ItemTemplate in your ItemsControl.
Your XAML should look like this:
<ItemsControl ItemsSource="...">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- properties of your object should go here -->
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

WPF Databinding stackpanel

Im a beginner in WPF programming, coming from .NET 2.0 C#.
Im trying to make a horizontal StackPanel which should be filled with data from a table in a database. The problem is that I want it to display an image with some text from the table below and then stack those two items horizontally.
Here's some pseudo-code to display what I want to do:
<StackPanel Orientation="horizontal" ItemsSource="{Binding Path=myTable}">
<StackPanel>
<Image Source="User.png"/>
<Label HorizontalAlignment="Center" Content="{Binding Path=UserName}"></Label>
</StackPanel>
</StackPanel>
I simply cannot figure oout how to do this.
Julien's answer is correct for your written description, however, looking at your XAML, it appears you are looking for something like the following:
<DataTemplate x:Key="UserDataTemplate">
<StackPanel>
<Image Source="User.png"/>
<Label HorizontalAlignment="Center" Content="{Binding Path=UserName}" />
</StackPanel>
</DataTemplate>
<ItemsControl x:Name="UserList" ItemTemplate="{StaticResource UserDataTemplate}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
You definately need an ItemsControl (or some derivation of) to bind your source to. Then you can change the the orientation by setting it's items panel (which I believe is a VirtualizingStackPanel with Vertical orientation by default) so just set it to a VirtualizingStackPanel with Horizontal Orientation. Then you can set the ItemsTemplate for each of your items to the layout you desire (an image stacked on top of text bound from your database).
Basically, you want to use a control capable of displaying an enumeration of objects. The control capable of this is the class ItemsControl and all of its descendants (Selector, ListBox, ListView, etc).
Bind the ItemsSource property of this control to a list of objects you want, here a list of users you've fetched from the database. Set the ItemTemplate of the control to a DataTemplate that will be used to display each item in the list.
Sample code:
In a Resources section (for example Window.Resources):
<DataTemplate x:Key="UserDataTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="User.png"/>
<Label HorizontalAlignment="Center" Content="{Binding Path=UserName}" />
</StackPanel>
</DataTemplate>
In your Window/Page/UserControl:
<ItemsControl x:Name="UserList" ItemTemplate="{StaticResource UserDataTemplate}" />
In your code behind:
UserList.ItemsSource = ... // here, an enumeration of your Users, fetched from your db

Categories