WPF control develop idea - c#

OK, I want to create a control that is like a Stackpanel with TextBlock on the left, something like:
The TextBlock need to be editable. So, the question is from whom I need to inherit to make that since cannot from Stackpanel?

That is basically a HeaderedItemsControl with a custom Template.
The template could be a Grid with two columns, one containing a rotated ContentPresenter which is bound to the header properties, on the right you would have an ItemsPresenter for the items.
e.g.
<Style TargetType="HeaderedItemsControl"> <!-- Implicitly applied -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="HeaderedItemsControl">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ContentPresenter ContentSource="Header">
<ContentPresenter.LayoutTransform>
<RotateTransform Angle="-90"/>
</ContentPresenter.LayoutTransform>
</ContentPresenter>
<ItemsPresenter Grid.Column="1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<HeaderedItemsControl Header="Lorem Ipsum" ItemsSource="ABCDEF"/>

Related

ContentPresenter - HorizonalAlignment not working

I have created a custom control for the simplified usage of label: control in forms:
My problem is that, no matter what is being put into my control content, I am unable to align it, using my template code. The only way it works is when I put HorizontalAlignment="Right" into the TextBox control directly:
<local:ControlEntry Label="Enter something">
<!-- I don't like having HorizontalAlignment here! -->
<TextBox Height="20" Width="200" HorizontalAlignment="Right" />
</local:ControlEntry>
However, I would rather want to do that behind the scenes in my control template.
I'm talking about this particular XAML code:
<Border x:Name="Container"
HorizontalAlignment="Stretch"
Grid.Column="1">
<ContentPresenter Width="{Binding ActualWidth, ElementName=Container}" HorizontalAlignment="Right" />
</Border>
First, I stretch the HorizontalAlignment, so what it would fill the width of the grid column cell, then I'm trying to apply the Right HorizontalAlignment on the ContentPresenter. Unfortunately, no effect is visible.
Full control code:
<Style TargetType="{x:Type local:ControlLine}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ControlLine}">
<!-- Create bottom content border -->
<Border BorderBrush="#000" BorderThickness="0,0,0,0.5">
<StackPanel>
<Grid>
<!-- Label -> Control -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Label -->
<TextBlock Grid.Column="0"
Margin="0,12,15,12"
VerticalAlignment="Center"
Text="{Binding Label, RelativeSource={RelativeSource TemplatedParent}}"
TextWrapping="WrapWithOverflow" />
<!-- Display content -->
<Border x:Name="Container"
HorizontalAlignment="Stretch"
Grid.Column="1">
<ContentPresenter Width="{Binding ActualWidth, ElementName=Container}" HorizontalAlignment="Right" />
</Border>
</Grid>
<!-- Description -->
<TextBlock Text="The control description" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How can I properly keep the width stretched and at the same time align the content of ContentPresenter to right?

XAML GridRow doesn't scale automatically when ErrorTemplate is displayed

I have a XAML window with a TextBox, and this TextBox has an ErrorTemplate.
The ErrorTemplate is shown below, and as you can see, I have an AdornedElementPlaceholder, followed by a textbox whose Text field is bound to the ErrorContent:
<ControlTemplate x:Key="ValidationErrorTemplateTextBlock" TargetType="{x:Type Control}">
<Border BorderBrush="Red" BorderThickness="1">
<StackPanel Orientation="Vertical">
<AdornedElementPlaceholder Name="AdornedElementPlaceholder" />
<TextBlock Text="{Binding ElementName=AdornedElementPlaceholder, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
FontSize="10"
Background="Red"
Foreground="White"
Padding="2" />
</StackPanel>
</Border>
</ControlTemplate>
<TextBox IsEnabled="{Binding SendMessage}"
Text="{Binding AutoMessageSubject, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource StyleBase}"
Validation.ErrorTemplate="{StaticResource ValidationErrorTemplateTextBlock}"
HorizontalAlignment="Stretch"
Grid.Row="3"
Grid.Column="1"
Grid.ColumnSpan="2" />
This works fine, except for one thing: the TextBox is inside a GridRow, with a Height="Auto". The row scales itself based on the textbox, but when the ErrorTemplate appears, with an extra TextBox on the bottom - the GridRow doesn't scale up to contain the new TextBox, and the new TextBox overlaps the elements below it.
How can I solve this?
Validation.ErrorTemplate: Gets or sets the ControlTemplate used to generate validation error feedback on the adorner layer.
This means that if you use Validation.ErrorTemplate, the validation errors are displayed on the layer above usual content, so the "second" TextBlock is displayed over the grid, not within the grid cell.
I would implement INotifyDataErrorInfo instead of semi-obsolete IDataErrorInfo, use a custom textbox style, and bind the visibility of the second TextBlock to HasErrors property. The example below uses a ToolTip instead of the second TextBlock:
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget}">
<ItemsControl DisplayMemberPath="ErrorContent" ItemsSource="{Binding Path=(Validation.Errors)}" />
</ToolTip>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
You might want to try adding Row/Column Definitions:
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

Resizeable multiple TextBoxes

I'm going to create some program that resizeable (with drag) multiple Textboxes.
But, I don't know how to build this layout. Is there know how to create drag layout?
It's not entirely clear what your exact specification is here. But the drawing makes it look like you want some cells within the grid to have grab handles for varying width, while others do not. For this purpose, you should be able to use the GridSplitter object.
For example:
<Window x:Class="TestSO36334781GridSplitter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Border BorderBrush="Black" BorderThickness="1">
<Grid>
<Grid.Resources>
<p:Style TargetType="GridSplitter">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="Width" Value="5"/>
<Setter Property="Height" Value="10"/>
<Setter Property="Background" Value="Black"/>
<!-- Offset the splitter visually so it's centered over the gridline -->
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="2.5" Y="0"/>
</Setter.Value>
</Setter>
</p:Style>
<p:Style TargetType="TextBox">
<Setter Property="Height" Value="30"/>
</p:Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Label1" Grid.Column="0"/>
<TextBlock Text="Label1" Grid.Column="1"/>
<TextBlock Text="Label1" Grid.Column="2"/>
<TextBox Grid.Row="1" Grid.Column="0"/>
<TextBox Grid.Row="1" Grid.Column="1"/>
<TextBox Grid.Row="1" Grid.Column="2"/>
<GridSplitter Grid.Row="1" Grid.Column="0"/>
<GridSplitter Grid.Row="1" Grid.Column="1"/>
<TextBox Grid.Row="2" Grid.ColumnSpan="3" Text="A wide textbox here"/>
</Grid>
</Border>
</StackPanel>
</Window>
The above shows a grid with three TextBox controls in the middle row, the widths of which can be modified by the user by dragging the GridSplitter between each of them. The labels above them (i.e. the TextBlock objects) are moved/resized as well, as they share the same column with each respective TextBox.
A fourth TextBox is shown, spanning three columns in the last row, to show how you can still have other grid elements independent of the splitters. I assume you can modify the basic idea to suit your specific needs.
Note that it's important you provide your specific formatting for the splitter objects, and that they appear after the controls they share grid elements with, so that they are above those controls in the z-order.
See also this Stack Overflow question: WPF user controlled grid column width
Addendum:
As hinted at in the (now deleted) comments by Joey, it is possible to place splitter controls without them having to share the cell with (and possibly obscuring) other elements in the grid. The following XAML snippet (i.e. just the Grid element) shows how that would work:
<Grid>
<Grid.Resources>
<p:Style TargetType="GridSplitter">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="Width" Value="5"/>
<Setter Property="Height" Value="10"/>
<Setter Property="Background" Value="Black"/>
</p:Style>
<p:Style TargetType="TextBox">
<Setter Property="Height" Value="30"/>
</p:Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Label1" Grid.Column="0"/>
<TextBlock Text="Label1" Grid.Column="2"/>
<TextBlock Text="Label1" Grid.Column="4"/>
<TextBox Grid.Row="1" Grid.Column="0"/>
<TextBox Grid.Row="1" Grid.Column="2"/>
<TextBox Grid.Row="1" Grid.Column="4"/>
<GridSplitter Grid.Row="1" Grid.Column="1" ResizeBehavior="PreviousAndNext"/>
<GridSplitter Grid.Row="1" Grid.Column="3" ResizeBehavior="PreviousAndNext"/>
<TextBox Grid.Row="2" Grid.ColumnSpan="5" Text="A wide textbox here"/>
</Grid>
The above eliminates the need for the RenderTransform, as each GridSplitter winds up centered in its own column. The ResizeBehavior is set to PreviousAndNext, so that dragging the splitter affects not the width of the column in which the splitter is contained, but instead the widths of the columns immediately before and after it.
It's possible that you could apply a DataGrid control in this scenario and get it to do what you want. But there's nothing in your question that suggests to me you need the full feature set of a DataGrid, or even that you'll be happy with some of the constraints that would involve (such as the way headings are formatted, and whether you can include other fixed-width elements in the layout).

ScrollIntoView in GridView doesn't work

the ScrollIntoView() method does not work for me, but I was able to narrow the problem a little.
We have changed the style for the GridView as follows:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GridView">
<Border>
<ScrollViewer x:Name="ScrollViewer" >
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="38" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" MinWidth="120"/>
</Grid.ColumnDefinitions>
<Border x:Name="NewGroupPlaceHolderFirst" Grid.Column="0" ... />
<ItemsPresenter Grid.Row="1" ... />
<Border x:Name="NewGroupPlaceHolderLast" Grid.Column="2" ... />
</Grid>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
When the ItemsPresenter stands alone within the ScrollViewers the method works.
but the borders are needed to create a new Group.
Is there a possible solution to get this method work?
regards,
oliver
You have to implement scrolling by yourself:
Find the ScrollViewer by examining the childrens of the GridView with the VisualTreeHelper.
Determine the horizontal/vertical offset of the item you want to scroll to.
Scroll to the position.
var offset = targetItem.TransformToVisual(scrollViewer).TransFormPoint(new Point(0,0)).X;
scrollViewer.ScrollToHorizontalOffset(offset);

How to Affect TabPanel Template's Position based on TabStripPlacement Property WPF

I created a control template for all TabControl in my application. The template places the TabPanel at the left of the tab item's main content.
<Style TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TabPanel Grid.Column="0"
Panel.ZIndex="1"
Margin="0,0,0,0"
IsItemsHost="True"
Background="Transparent" />
<Border Grid.Column="1"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="0,12,12,12">
<Border.Background>
<SolidColorBrush Color="Green" />
</Border.Background>
<ContentPresenter ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This is initially what I want my tab controls to look like. But when I set the TabStripPlacement property to Top, it still stays in the left side. Is there a way to define in the control template to adjust the position of TabPanel based on the TabStrip property? Thanks in advanced.
You need to use triggers in your style that react to the change in the TabStripPlacement property.
This stackoverflow post provides an example of this.

Categories