Rotating Controls within a StackPanel - c#

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>

Related

How can I avoid overriding a window-wide style in WPF?

I'm using the MaterialDesign nuget package for my WPF application.
According to the tutorial, by applying window-wide properties, every element will inherit the MaterialDesign style.
However, if I apply a custom style to an element, that element loses its MaterialDesign style.
I can get around this by applying inline styles, but that is very repetitive and error prone.
I think the picture shows it better:
And here is my xaml:
<Window
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:TestApp"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" x:Class="TestApp.MainWindow"
mc:Ignorable="d"
TextElement.Foreground="{DynamicResource MaterialDesignBody}"
TextElement.FontWeight="Regular"
TextElement.FontSize="13"
TextOptions.TextFormattingMode="Ideal"
TextOptions.TextRenderingMode="Auto"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="{DynamicResource MaterialDesignFont}"
Title="MainWindow" Height="450" Width="600">
<!--All of the above is meant to apply Material Design to the entire Window-->
<StackPanel>
<StackPanel.Resources>
<!--This style overrides the Material Design style entirely,
instead of just Margin and Horizontal Alignment-->
<Style x:Key="SpacedButton" TargetType="Button">
<Setter Property="Margin" Value="0 10"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</StackPanel.Resources>
<!--Material Design only works by applying properties directly to elements-->
<Button Content="Button #1" HorizontalAlignment="Center" Margin="0 10"/>
<Button Content="Button #2" Style="{DynamicResource SpacedButton}"/>
<Button Content="Button #3" Style="{DynamicResource SpacedButton}"/>
<Button Content="Button #4" HorizontalAlignment="Center" Margin="0 10"/>
</StackPanel>
</Window>
As you can see, only elements with inline properties keep the MaterialDesign style, but by applying a custom style, the MaterialDesign style is lost.
How can I make sure that MaterialDesign is applied to every element, while still being able to override specific properties with custom styles?
Sorry if some of the terminology is wrong, I'm pretty new to WPF.
Use Style.BasedOn to inherit properties from whichever other style is in scope:
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
...etc....
Or a specific one:
<Style x:Key="ThisStyleKey" TargetType="{x:Type Button}" BasedOn="{StaticResource OtherStyleKey}">
...etc....

Change TextBox Template without losing classic behavior

I've create a custom control which inherit from WPF TextBox.
My Control Template simply add a small button on textbox in order to delete its text quicker.
However, I've noticed that when my textBox got the focus, its border doesn't change (blue color) as is the case for classic textbox.
I would preserver all aspect of original textBox, just like the border when the control get focus.
Am I missing something?
##EDIT
<TextBox x:Class="XTextBox.WKTextBox"
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"
mc:Ignorable="d"
Height="23" Width="200"
>
<TextBox.Resources>
<ControlTemplate x:Key="IconButton" TargetType="{x:Type ToggleButton}">
<Border>
<ContentPresenter />
</Border>
</ControlTemplate>
</TextBox.Resources>
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<Border BorderThickness="1" BorderBrush="DarkGray">
<ScrollViewer x:Name="PART_ContentHost" />
</Border>
<ToggleButton Template="{StaticResource IconButton}"
MaxHeight="21"
Margin="-1,0,0,0"
Name="imgButton"
Focusable="False"
IsChecked="False">
<Image Name="imgClearText" Source="Images\x.png" Stretch="Uniform" Opacity="0.5" Visibility="Visible" HorizontalAlignment="Right" >
</Image>
</ToggleButton>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TextBox.Style>
Unfortunately, you can't simply Replace part of default template in WPF without loosing functionallity.
I believe the easiest solution would be to donwload Blend (it comes with VS2015). Open it, create an emty textbox and edit its template:
Blend will make a copy of default template, so you won't loose any of your default behaviour, like selection, focus etc.
Then you can save a project, open it in VS and refactor it as you want. Like moving style to dictionary or something.
You can manually get the same effect by adding handlers for the GotFocus & LostFocus events of the Border and set the highlight colors you want there.
<Border BorderThickness="1" BorderBrush="DarkGray" LostFocus="Border_LostFocus" GotFocus="Border_GotFocus">
and in your .cs file
private void Border_LostFocus(object sender, RoutedEventArgs e)
{
((Border)sender).BorderBrush = new SolidColorBrush(Colors.DarkGray);
}
private void Border_GotFocus(object sender, RoutedEventArgs e)
{
((Border)sender).BorderBrush = new SolidColorBrush(Colors.LightBlue);
}

How do I assign an EventTrigger to a reusable Button in its parent?

My reusable Button is basically a single button, of which ControlTemplate consists of a TextBlock and an Image. The Text property of TextBlock binds to a DependencyProperty to be exposed; similarly, the Source property of Image binds to a DependencyProperty. Here is the code for this Button.
<Button x:Class="Core.Resource.UserControlResource.NavigationButton1"
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:Core.Resource.UserControlResource"
mc:Ignorable="d"
x:Name="myself">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="63"></ColumnDefinition>
<ColumnDefinition Width="63"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Name="IconImage" Height="42" Width="42" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Source="{Binding ElementName=myself, Path=ScreenIcon}" />
<TextBlock Grid.Column="1" Text="{Binding ElementName=myself, Path=ScreenTitle}" FontSize="25" TextWrapping="Wrap" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
where ScreenTitle and ScreenIcon are the aforementioned DependecyProperty.
Now, I want to use this Button in its "parent", a UserControl. The code will be like
<UserControl x:Class="Core.ParentControl"
x:Name="parent"
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:usrCtrlResrc="clr-namespace:Core.Resource.UserControlResource;assembly=Core.Resource"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32"/>
<ColumnDefinition Width="32"/>
<Grid.ColumnDefinitions>
<usrCtrlResrc:NavigationButton1 ScreenTitle="sample Screen Title" ScreenIcon="/Core.Resource;component/MediaResource/pencilEdit.png">
<TextBlock Grid.Column="1" Name="txtBlk" Text="SampleSample"/>
</Grid>
However, in order to add reactions when the Button is clicked (say to change the Grid.ColumnSpan property of TextBlock "txtBlk" to 2), what I want to do else is assign EventTriggers to my reusable Button in the "parent". I initially thought of two ways, but none of them works.
In my reusable Button, bind Style.Triggers to a DependencyProperty to get exposed to its "parent". However, it pops up "The property Triggers does not have an accessible setter".
Move the Style of my reusable Button to a ResourceDictionary and assign a Key for the "parent" to use. However, by doing this, I am not sure how to handle my two DependencyProperty, because it is not supposed to have code-behind for a ResourceDictionary file.
Any other workarounds? Thanks in advance.
I finally resolve this problem by directly override its Triggers. Code is as below.
<usrCtrlResrc:NavigationButton1 ScreenTitle=... ScreenIcon=...>
<usrCtrlResrc:NavigationButton1.Triggers>
<EventTrigger RoutedEvent="usrCtrlResrc:NavigationButton1.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
...
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</usrCtrlResrc:NavigationButton1.Triggers>
</usrCtrlResrc:NavigationButton1>

ScrollViewer Not Scrolling To Bottom Using Avalonedit

I've created a user control which uses the avalonedit control. The XAML for the user control is:
<UserControl x:Class="CodeNote.UserControl1"
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:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
xmlns:editing="clr-namespace:ICSharpCode.AvalonEdit.Editing">
<UserControl.Resources>
<ControlTemplate x:Key="TextBoxBaseControlTemplate1" TargetType="{x:Type TextBoxBase}">
<Border Background="{TemplateBinding Background}"
x:Name="Bd" BorderBrush="#D1D1E8"
BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="10, 10, 0, 0">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" TargetName="Bd"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
<Trigger Property="Width" Value="Auto">
<Setter Property="MinWidth" Value="100"/>
</Trigger>
<Trigger Property="Height" Value="Auto">
<Setter Property="MinHeight" Value="20"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Margin="0,0,0,10">
<TextBox x:Name="txtTitle" VerticalContentAlignment="Center" Template="{StaticResource TextBoxBaseControlTemplate1}" FontWeight="Bold" Margin="5,5,5,0" Padding="5, 3, 5, 2" FontFamily="Arial" FontSize="12" BorderThickness="1,1,1,0" Background="#FFF0F0F0"></TextBox>
<Border BorderThickness="1" CornerRadius="0,0,10,10" BorderBrush="#D1D1E8" Background="#FFF7F7F9" Margin="5,0,5,0" Padding="5,5,5,5">
<avalonEdit:TextEditor
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
Name="textEditor"
FontFamily="Courier New"
SyntaxHighlighting="Java"
Background="#FFF7F7F9"
ShowLineNumbers="True"
VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility ="Hidden"
WordWrap="True"
FontSize="12pt"/>
</Border>
</StackPanel>
</Grid>
</UserControl>
The main window contains the following StackPanel within a ScrollViewer within a Grid
<Window x:Class="CodeNote.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
xmlns:editing="clr-namespace:ICSharpCode.AvalonEdit.Editing"
x:Name="mainWin"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Visible" CanContentScroll="False">
<StackPanel Grid.Row="0" Margin="10,10,10,0" VerticalAlignment="Top" x:Name="container">
</StackPanel>
</ScrollViewer>
</Grid>
</Window>
The user should be able to programmatically add the usercontrol to the main window, which is accomplished in the main window code behind with:
UserControl1 avEditor = new UserControl1(); container.Children.Add(avEditor);
My problem is that the scrollviewer does not scroll to the bottom when the avalonedit control's contents becomes too vertically large for the window. The insertion point simply disappears off the bottom of the visible window and the scroll position stays at the top.
I notice that the scroll works correctly if I add a regular textbox instead of a avalonedit control.
How can I rectify this (I am very new to WPF)
Please note that the program needs to be able to add multiple text input controls to this scroll viewer, e.g avalonedit followed by a text box followed by another textbox followed by another avalonedit. Therefore, I can't just use the scrollviewer's ScrollToBottom method because the control being edited might not be the last control in the scrollviewer.
I simply need the insertion point to remain onscreen and the window to scroll accordingly.
Seems like Caret.BringCaretToView() only scrolls AvalonEdit itself, it doesn't send BringIntoView request up the visual tree.
I think you'd need to change the AvalonEdit source code to fix this.
I am no expert but below is what I use for a pseudo tail using AvalonEdit. The _scroll is just a checkbox the user can uncheck to stop scrolling.
private void PullAndLoadData()
{
Task.Delay(1000);
TailFile = TailFile + _logStream.Read();
Dispatcher.Invoke(() => { textEditor.Text = TailFile; });
if (_scroll) Dispatcher.Invoke(() => { textEditor.ScrollToEnd(); });
}

WPF freeform border control

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.

Categories