WPF freeform border control - c#

i have to develop a wpf control which shall have the same behaviour as the well known border.
The shape of the control shall be the new part. Every definable closed path shall be used to define the appearence of the control.
I need help to achieve this.
Currently i have no idea how to interchange the rectangle(??) with the closed path.
Any help would be highly appreciated.

Edit Here goes direct answer to your question. We will write a ContentControl derived class, with very flexible form of border. Basis for this idea lies in OpacityMask.
If you would like to know more about this approach take a look on example from Chris Cavanagh's solution for rounded corners.
Step 1. Create custom control FreeFormContentControl:
FreeFormContentControl.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfApplication5
{
public class FreeFormContentControl : ContentControl
{
public Geometry FormGeometry
{
get { return (Geometry)GetValue(FormGeometryProperty); }
set { SetValue(FormGeometryProperty, value); }
}
public static readonly DependencyProperty FormGeometryProperty =
DependencyProperty.Register("FormGeometry", typeof(Geometry), typeof(FreeFormContentControl), new UIPropertyMetadata(null));
static FreeFormContentControl()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(FreeFormContentControl),
new FrameworkPropertyMetadata(typeof(FreeFormContentControl))
);
}
}
}
Themes\Generic.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication5">
<Style TargetType="{x:Type local:FreeFormContentControl}">
<Setter Property="FormGeometry"
Value="M0,0 L1,0 1,1 0,1z" />
<Setter Property="Background"
Value="Black" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:FreeFormContentControl}">
<Grid>
<Path Name="mask"
Data="{TemplateBinding FormGeometry}"
Fill="{TemplateBinding Background}" />
<Grid>
<Grid.OpacityMask>
<VisualBrush Visual="{Binding ElementName=mask}" />
</Grid.OpacityMask>
<ContentPresenter />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
More reading on custom controls can be found on CodeProject.
Step 2. Usage. Now you can place any content inside this control. Its default shape is rectangle. So the following code will result in regular StackPanel UI:
<Window x:Class="WpfApplication5.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:WpfApplication5"
Title="Window1"
Height="300"
Width="300">
<Grid>
<cc:FreeFormContentControl>
<StackPanel>
<Button Content="Any" />
<Button Content="Content" />
<TextBlock Text="Goes" />
<TextBox Text="Here" />
</StackPanel>
</cc:FreeFormContentControl>
</Grid>
</Window>
But if you define custom FormGeometry you'll get custom shape. For example, the following form geometry presents inner controls inside a diamond:
<cc:FreeFormContentControl FormGeometry="M0,0.5 L0.5,0 1,0.5 0.5,1z">
To read more about geometry definition from XAML, read corresponding section on MSDN: Path Markup Syntax.
The last thing to mention here, is that you don't have to specify or calculate concrete pixel values of your FormGeomtry. Grid makes this trick possible. So think of it as of percentage. I.e. 1 == full width or height. 0.5 == half of available width/hight and so on.
Hope this helps.

Related

Header property is not working in UWP CustomTextBox

In UWP I have created CustomTextBox which is derived from TextBox control. In my CustomTextBox, I have used various properties but some properties are working and some are not working.
Below properties are working fine,
Width Height BorderBrush, etc,.
Below properties are not working,
Header Text, etc,. Please find code snippet on below,
MyTextBox.cs
public sealed class MyTextBox : TextBox
{
public MyTextBox()
{
this.DefaultStyleKey = typeof(MyTextBox);
}
}
Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SfMaskedEdit_header">
<Style TargetType="local:MyTextBox" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyTextBox">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MainPage.xaml
<Page
x:Class="SfMaskedEdit_header.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SfMaskedEdit_header"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid x:Name="grid1">
<local:MyTextBox x:Name="myTextBox" Text="Hello" Header="MyTextBox" Width="200" Height="40"
BorderBrush="Blue" BorderThickness="2" Background="Pink"/>
</Grid>
</Page>
Please use the below sample for further information,
Sample: MyTextBox
The reason is that you have overridden the default template and hence removed all the elements that normally make up a TextBox.
To understand this, it is important to make it clear that UWP controls usually just provide a behavior, but are themselves made up of multiple other controls in their template (the terminology is usually that XAML controls are look-less by default). And these controls are providing the visual representation of the control. For example, in the case of TextBox, the template is made up of Border for the borders around it, TextBlock for the placeholder text, Button for clearing out the content, a ScrollViewer where the Text input is rendered and a ContentPresenter for the header and so on.
Your custom TextBox is in fact just the Border itself, which means you have essentially lost the capabilities you mentioned because there are no controls to surface them visually.
To fix your problem, I suggest starting with the default TextBox template (by copying and pasting it to your own style. And then just editing the parts that you want to change. This way you will start from the fully functional state and can decide what to change while retaining the capabilities of the control.
You can find the default template in C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\{version}\Generic\generic.xaml. You can search for <Style TargetType="TextBox"> to find the default TextBox style and template.

Events are triggered twice on a CustomControl

The problem is that the KeyDown event is triggered twice, the first one comes from the CustomTextBox named textSource inside the Style; the second, from control in the MainWindow named "CTbox".The linked question provides a solution where you filter on the EventHandler OnKeyDown the source e.Source != "textSource" which is working fine:
if (e.Source is CustomTextBox sourceTextBox && sourceTextBox.Name.Equals("textSource"))
{
return;
}
Basically I would like to know if there is any better solution to this and if someone can explain the reason why is this happening and how can be avoid it.
The style is mean to create a Hint Text or WaterMark in the CustomTextBox without relaying in the Focus events
Thanks in advance.
Following the code to create a Minimal, Complete, and Verifiable example of this behaviour
CustomTextBox Class:
public class CustomTextBox : TextBox
{
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
}
}
MainWindow:
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="CustomTextBoxStyle"
TargetType="{x:Type local:CustomTextBox}">
<Setter Property="Foreground"
Value="#FF414042" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomTextBox}">
<Border Name="Border"
BorderBrush="#FF348781"
BorderThickness="0,0,0,4"
CornerRadius="2">
<ScrollViewer x:Name="PART_ContentHost"
Margin="0" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled" />
<VisualState x:Name="ReadOnly" />
<VisualState x:Name="MouseOver" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:CustomTextBox}"
BasedOn="{StaticResource CustomTextBoxStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomTextBox}">
<Grid>
<local:CustomTextBox
Text="{TemplateBinding Text}"
x:Name="textSource"
Background="Transparent"
Panel.ZIndex="2"
Style="{StaticResource CustomTextBoxStyle}"/>
<TextBlock Text="{TemplateBinding Tag}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground"
Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Text, Source={x:Reference textSource}}"
Value="">
<Setter Property="Foreground"
Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<local:CustomTextBox x:Name="CTbox" Tag="Hint Text Example" Height="25" Width="258"/>
</Grid>
</Window>
So you're getting two executions of your OnKeyDown handler because your XAML essentially builds a CustomTextBox within a CustomTextBox. With the routing strategy of WPF events and your visual tree looking like this:
the KeyDown event naturally will fire in both places (textsource, then bubble up to CustomTextBox). Side note, if you overrode OnPreviewKeyDown, you should get the reverse order - CTbox tunneling down to textSource.
To answer the second part of your question - how to avoid this, I think perhaps you should rethink your implementation here. My questions would be:
Should you even use an editable control (your CustomTextBox in this case) for your hinttext overlay in your control template? Since the hinttext should only be readable, perhaps a TextBlock would suffice.
Should you override the ControlTemplate for customtextbox to get this HintText functionality? Perhaps the better way is just to create a UserControl that provides this functionality. i.e. Grid containing your custom textbox and a textblock overlaying it. This will prevent you from having a visual tree with nested CustomTextBox's.
If you need a ControlTemplate so you can reuse it, why not make it the default control template of your CustomTextBox? If your "Tag" property is not bound, the hinttext will just naturally not show. This way you won't have nested CustomTextBoxes causing duplicate execution of OnKeyDown as well!
Why do you even need a CustomTextBox? Do you have other override code and behavior that you're not showing that requires this? I'm guessing this is because you showed a minimal sample and there's more - but just thought I'd ask :)
EDIT for comment
Given your clarifications/questions, I would've approached solving this via a custom control. I know what you have is technically a custom control, but I'm talking about the kind that comes with a themes\generic.xaml file :). If you're not familiar, I recommend creating a new VS project and making it of the "WPF custom control library" template. Then you should be able to add a new class of the template "Custom Control (WPF)". You'll see that VS has generated a themes\generic.xaml file for you - this is where you would hold the controltemplate for your CustomTextBox. I would get the default control template of a TextBox, and add in a TextBlock that's not hit test visible (so a user can click through to your editable area) and set a TemplateBinding on it for the HintText. You'll then be able to reuse this custom control everywhere(as it's compiled in a separate dll... you can also opt to keep it within your current project too), get the default behaviors of textbox that you didn't override, and won't have nested CustomTextBoxes.

ContentControl does not show content in ControlTemplate

I need to create a custom control similar to WPF GroupBox. I started from the standard WPF Custom Control Library template in VS 2015 and defined my CustomGroupBox control like this:
public class CustomGroupBox : ContentControl
{
static CustomGroupBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomGroupBox), new FrameworkPropertyMetadata(typeof(CustomGroupBox)));
}
}
Then added the following minimal set of lines to develop my custom GroupBox according to the specification:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomGroupBox">
<Style TargetType="{x:Type local:CustomGroupBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomGroupBox}">
<Border Background="{TemplateBinding Background}"
BorderBrush="Gray" BorderThickness="3" CornerRadius="3">
<ContentControl />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
To debug and develop, I created a test form for my custom control:
<Window x:Class="CustomGroupBoxClient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CustomGroupBoxClient"
xmlns:ctrl="clr-namespace:CustomGroupBox;assembly=CustomGroupBox"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ctrl:CustomGroupBox Margin="5">
<StackPanel Orientation="Vertical">
<TextBlock>Text Block #1</TextBlock>
<TextBlock>Text Block #2</TextBlock>
</StackPanel>
</ctrl:CustomGroupBox>
</Grid>
</Window>
However, when I launch this form, I see the border of my custom control but not the content (2 TextBlock's):
I've re-read many manuals and articles related to this topic, but still can't figure out why ContentControl in my ControlTemplate does not display the specified content. How to solve my problem?
Try using ContentPresenter instead of ContentControl in your template:
<ContentPresenter />
By default, ContentPresenter finds the Content property of its templated parent and displays whatever it finds there. You could change the name of the property it looks for by changing its ContentSource value...
<ContentPresenter ContentSource="FooBar" />
...but since you're inheriting from ContentControl, the default is probably what you want.

Rotating Controls within a StackPanel

I am trying to create my own expandable/collapsible menu (creatively called ExpandingMenu) in my first WPF project. I already have one user control consisting of a 2-row Grid (row 1 is a button to collapse and expand the control, row 2 is a StackPanel for rotating ToggleButtons, which is where I'm currently stuck). For my rotating buttons, I have just decided to make them their own UserControls as well.
The Buttons (I'm calling them ExpandingMenuButtons) are no more than a ToggleButton in a 1x1 Grid (I'm thinking of doing it this way since I may want to add some extra custom logic to these buttons after I get the standard behavior sorted out). I can add them to my menu control successfully, I can even get them to rotate via a RenderTransform on the Grid.
However, as you can probably tell, they swing up when they rotate. This causes them to not only be too high, but they also likely extend to far up.
This is what I currently have, before attempting rotations, it is behaving as expected.
This is what I'm trying to accomplish (edited using the magic of paint). I can get this correct behavior in my Menu control (tan areas), but I've mangled the expand/contract event in the meantime for testing purposes...
What I can do when I rotate 1 button (Like I mentioned earlier, I've mangled some behavior for my testing purposed, so each button is set to rotate on click, not all at once like you may expect). As you can see, this button has swung out from where it originally was. buttons higher up will swing partially/completely out of view. Instead, I would like them to rotate into the proper place. once I get one to work right, I assume it will be simple to get the rest behaving the same way, which I why I'm trying things this way..
My button code is below:
<UserControl x:Class="App.Controls.ExpandingMenuButton"
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:App.Controls"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="100">
<Grid Name="ButtonGrid" Height="30">
<ToggleButton Name="MenuButton" Background="Aqua" BorderThickness="0 0 0 1" Click="MenuButton_Click" Content="TEST"></ToggleButton>
</Grid>
</UserControl>
the only "real" code in ExpandingMenuButton.xaml.cs so far:
private void MenuButton_Click(object sender, RoutedEventArgs e)
{
//I know this is not practical, it is used for quick testing.
ButtonGrid.RenderTransform = new RotateTransform(-90);
}
My menu code so far:
<UserControl x:Class="App.Controls.ExpandingMenu"
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:App.Controls"
mc:Ignorable="d"
Name="ucExpandingMenu"
MinWidth="32"
d:DesignHeight="300" d:DesignWidth="100">
<UserControl.Resources>
<SolidColorBrush x:Key="BackColor" Color="PeachPuff"/>
<!-- This style is used for buttons, to remove the WPF default 'animated' mouse over effect. http://stackoverflow.com/a/4182205/2957232 -->
<Style x:Key="ExpandButtonStyle" TargetType="Button">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Name="border"
BorderThickness="0 0 0 1"
BorderBrush="Black"
Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Name="MenuPanel" Width="100" HorizontalAlignment="Left" Background="{DynamicResource BackColor}" Grid.Row="1">
<!--Contents will go here-->
</StackPanel>
<Button Style="{StaticResource ExpandButtonStyle}" Width="100" Height="32" FontSize="18" VerticalAlignment="Center" HorizontalAlignment="Stretch" Panel.ZIndex="1" Background="{DynamicResource BackColor}" BorderThickness="0" Click="Button_Click" Content="ยป"></Button>
<Button Name="DummyFocus" Panel.ZIndex="0" Height="0" Width="0"></Button>
</Grid>
</UserControl>
And again, the only "real" code in this class so far:
private void Button_Click(object sender, RoutedEventArgs e)
{
MenuPanel.Children.Add(new ExpandingMenuButton("TEST ITEM"));
}
Please excuse my lack of WPF knowledge, I'm trying to come from a world of Winforms, where even there I have a lack of knowledge when it comes to this sort of thing. I know the code looks kinda funny, but hopefully the images show what I'm after here. So far I'm just testing this in a dummy window with only a grid and the HorizontalAlignment set to "Left". Nothing fancy.
Use LayoutTransform. Using RenderTransform does not affect how other controls (including parent) are being rendered/laid out; LayoutTransform does.
Shift the transform to the whole UserControl. You can probably specify directly in XAML instead.
Example:
<UserControl.LayoutTransform>
<RotateTransform Angle="-90" />
</UserControl.LayoutTransform>

Access `DependencyProperty` via XAML and evaluate it?

I just created a DependencyProperty for my custom button.
The property is called IsChecked.
If IsChecked == true my button shall change its background color to something else and keep this color until IsChecked == false.
Here is my DependencyProperty:
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register("IsChecked", typeof(bool), typeof(MainMenuButton), new PropertyMetadata(false));
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
Next I have a ControlTemplate for this button:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTemplate x:Key="MainMenuButtonStyle" TargetType="{x:Type UserControl}">
<ControlTemplate.Resources>
/* Some storyboards */
</ControlTemplate.Resources>
<Grid x:Name="grid">
<Grid.Background>
<SolidColorBrush Color="{DynamicResource MainUI_MainMenuButtonBackground}"/>
</Grid.Background>
</Grid>
<ControlTemplate.Triggers>
/* Some triggers */
</ControlTemplate.Triggers>
</ControlTemplate>
My problem is now how I can access IsChecked and based on the current value of it change the background of grid? Haven't done that before only tried it some time ago and totally failed.
Thanks in advance :)
Edit:
Here is the UserControl aswell:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="IFCSMainInterface.MainMenuButton"
x:Name="UserControl"
d:DesignWidth="640" d:DesignHeight="480" Width="272" Height="110" Template="{DynamicResource MainMenuButtonStyle}">
<Grid x:Name="LayoutRoot"/>
I think the problem is here:
<ControlTemplate x:Key="MainMenuButtonStyle" TargetType="{x:Type UserControl}">
You must set the correct type in order to use its dependency property.
Write in your ResourceDictionary the namespace of the class, and put your type correctly in control template, eg:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:ProjectName.NameSpace">
<ControlTemplate TargetType="my:MainMenuButton" x:Key="MainMenuButtonStyle">
<!-- ... -->
</ControlTemplate>
</ResourceDictionary>
Edit (now that was explained):
You're trying to define a template in xaml own control
That does not seem quite right to do, I suggest looking for other approaches, such as creating a control that uses a Generic.xaml
( http://utahdnug.org/blogs/xamlcoder/archive/2007/12/13/building-custom-template-able-wpf-controls.aspx )
but if you want to use the dependency properties (the way you're doing) you can try the following code (for example):
<UserControl x:Class="SamplesStack.Controls.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:SamplesStack.Controls">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid x:Name="LayoutRoot">
<ContentPresenter />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsChecked}" Value="True">
<Setter TargetName="LayoutRoot" Property="Background" Value="Red"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Template>
This will make your control work as expected. Though not think very cool use DataTrigger.

Categories