Rotating a border doesn't change window size - c#

I try to rotate a Border and have the MainWindow change his size based on the new space taken by the Border rotation.
I've set SizeToContent="WidthAndHeight" but window size does't take change when I rotated the border.
Do I need to programmatically set the Width and Height for the MainWindow or this can be achieved changing the xaml code in some other way?
My xaml code:
<Window x:Class="MyClass.MainWindow"
WindowStyle="None" AllowsTransparency='True'
Topmost='False' Background="Transparent" ShowInTaskbar='False'
SizeToContent="WidthAndHeight" WindowStartupLocation="Manual">
<Border Name="MyBorder"
BorderBrush="Transparent"
Background="Transparent"
HorizontalAlignment="Left"
VerticalAlignment="Top"
RenderTransformOrigin="0.5,0.5">
</Border>
</Windows>
My c# code on Window_KeyDown:
# RotateTransform rt = new RotateTransform() is declared at class level.
if (e.Key == Key.I)
{
if (rt.Angle + 1 < 360)
{
rt.Angle += 1;
}
else
{
rt.Angle = 0;
}
MyBorder.RenderTransform = rt;
}

Use LayoutTransform instead of RenderTransform
From MSDN: Transforms Overview
LayoutTransform – A transform that is applied before the layout pass. After the transform is applied, the layout system processes the
transformed size and position of the element.
RenderTransform – A transform that modifies the appearance of the element but is applied after the layout pass is complete. By using the
RenderTransform property instead of the LayoutTransform property, you
can obtain performance benefits.
Example
<Border Name="MyBorder"
BorderBrush="Transparent"
Background="Transparent"
HorizontalAlignment="Left"
VerticalAlignment="Top"
RenderTransformOrigin="0.5,0.5">
<Border.LayoutTransform>
<RotateTransform Angle="90"/>
</Border.LayoutTransform>
</Border>
So in your case
RotateTransform rt = new RotateTransform(0.0, 0.5, 0.5);
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.I)
{
if (rt.Angle + 1 < 360)
{
rt.Angle += 1;
}
else
{
rt.Angle = 0;
}
MyBorder.LayoutTransform = rt;
}
}}

Related

WrapPanel scrollbars

Simple xaml:
<WrapPanel Orientation="Vertical">
<Ellipse Width="100" Height="100" Fill="Red" />
<Ellipse Width="100" Height="100" Fill="Yellow" />
<Ellipse Width="100" Height="100" Fill="Green" />
</WrapPanel>
Resizing window:
How to show vertical and horizontal scrollbars when content doesn't fit?
Note: this should work for any content.
I tried to put it into ScrollViewer:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<WrapPanel Orientation="Vertical">
...
</WrapPanel>
</ScrollViewer>
but then WrapPanel stops wrapping anything (always one column):
The problem here is that ScrollViewer gives (NaN, NaN) size to it child, so wrapping never occurs.
I tried to fix it by binding scroll viewer available height to max height of panel:
<ScrollViewer ...>
<WrapPanel MaxHeight="{Binding ViewportHeight, RelativeSource={RelativeSource AncestorType=ScrollViewer}}" ...>
This will limit panel height (not NaN anymore), so wrapping now occurs. But because this also adjust the height of panel - the vertical scrollbar will never appears:
How to add vertical scrollbar?
In my case WrapPanel is vertical, means it will fill columns as much as it can and then wrap to a new column from left to right. Scrollbars are needed when children doesn't fit either vertically (when available space is less than children height) or horizontally.
The idea thought can be used for a standard (horizontal) WrapPanel: from left to right, creating new rows when full. Absolutely same problem will arise (just tried it).
That sort of behavior is not possible with a WrapPanel without setting explicitly its Height/MinHeight for a Vertical orientation or Width/MinWidth for a Horizontal orientation. The ScrollViewer will only show the scrollbars when the FrameworkElement this scroll viewer wraps doesn't fit into the viewport.
You can create your own wrap panel that calculates its minimum size based on its children.
Alternatively, you can implement a Behavior<WrapPanel> or an attached property. This won't be as easy as just adding a couple of XAML tags, as you might expect.
We have solved this issue with an attached property. Let me give you an idea of what we did.
static class ScrollableWrapPanel
{
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ScrollableWrapPanel), new PropertyMetadata(false, IsEnabledChanged));
// DP Get/Set static methods omitted
static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var panel = (WrapPanel)d;
if (!panel.IsInitialized)
{
panel.Initialized += PanelInitialized;
}
// Add here the IsEnabled == false logic, if you wish
}
static void PanelInitialized(object sender, EventArgs e)
{
var panel = (WrapPanel)sender;
// Monitor the Orientation property.
// There is no OrientationChanged event, so we use the DP tools.
DependencyPropertyDescriptor.FromProperty(
WrapPanel.OrientationProperty,
typeof(WrapPanel))
.AddValueChanged(panel, OrientationChanged);
panel.Unloaded += PanelUnloaded;
// Sets up our custom behavior for the first time
OrientationChanged(panel, EventArgs.Empty);
}
static void OrientationChanged(object sender, EventArgs e)
{
var panel = (WrapPanel)sender;
if (panel.Orientation == Orientation.Vertical)
{
// We might have set it for the Horizontal orientation
BindingOperations.ClearBinding(panel, WrapPanel.MinWidthProperty);
// This multi-binding monitors the heights of the children
// and returns the maximum height.
var converter = new MaxValueConverter();
var minHeightBiding = new MultiBinding { Converter = converter };
foreach (var child in panel.Children.OfType<FrameworkElement>())
{
minHeightBiding.Bindings.Add(new Binding("ActualHeight") { Mode = BindingMode.OneWay, Source = child });
}
BindingOperations.SetBinding(panel, WrapPanel.MinHeightProperty, minHeightBiding);
// We might have set it for the Horizontal orientation
BindingOperations.ClearBinding(panel, WrapPanel.WidthProperty);
// We have to define the wrap panel's height for the vertical orientation
var binding = new Binding("ViewportHeight")
{
RelativeSource = new RelativeSource { Mode = RelativeSourceMode.FindAncestor, AncestorType = typeof(ScrollViewer)}
};
BindingOperations.SetBinding(panel, WrapPanel.HeightProperty, binding);
}
else
{
// The "transposed" case for the horizontal wrap panel
}
}
static void PanelUnloaded(object sender, RoutedEventArgs e)
{
var panel = (WrapPanel)sender;
panel.Unloaded -= PanelUnloaded;
// This is really important to prevent the memory leaks.
DependencyPropertyDescriptor.FromProperty(WrapPanel.OrientationProperty, typeof(WrapPanel))
.RemoveValueChanged(panel, OrientationChanged);
}
private class MaxValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.Cast<double>().Max();
}
// ConvertBack omitted
}
}
It is maybe not the easiest way, and there are a little bit more lines that just a few XAML tags, but it works flawlessly.
You have to be careful with the error handling though. I've just omitted all the checks and exception handling in the sample code.
The usage is simple:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<WrapPanel Orientation="Vertical" local:ScrollableWrapPanel.IsEnabled="True">
<!-- Content -->
</WrapPanel>
</ScrollViewer
It seems monitoring for children is one of important task to achieve wanted. So why not creating custom panel:
public class ColumnPanel : Panel
{
public double ViewportHeight
{
get { return (double)GetValue(ViewportHeightProperty); }
set { SetValue(ViewportHeightProperty, value); }
}
public static readonly DependencyProperty ViewportHeightProperty =
DependencyProperty.Register("ViewportHeight", typeof(double), typeof(ColumnPanel),
new FrameworkPropertyMetadata(double.PositiveInfinity, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));
protected override Size MeasureOverride(Size constraint)
{
var location = new Point(0, 0);
var size = new Size(0, 0);
foreach (UIElement child in Children)
{
child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
if (location.Y != 0 && ViewportHeight < location.Y + child.DesiredSize.Height)
{
location.X = size.Width;
location.Y = 0;
}
if (size.Width < location.X + child.DesiredSize.Width)
size.Width = location.X + child.DesiredSize.Width;
if (size.Height < location.Y + child.DesiredSize.Height)
size.Height = location.Y + child.DesiredSize.Height;
location.Offset(0, child.DesiredSize.Height);
}
return size;
}
protected override Size ArrangeOverride(Size finalSize)
{
var location = new Point(0, 0);
var size = new Size(0, 0);
foreach (UIElement child in Children)
{
if (location.Y != 0 && ViewportHeight < location.Y + child.DesiredSize.Height)
{
location.X = size.Width;
location.Y = 0;
}
child.Arrange(new Rect(location, child.DesiredSize));
if (size.Width < location.X + child.DesiredSize.Width)
size.Width = location.X + child.DesiredSize.Width;
if (size.Height < location.Y + child.DesiredSize.Height)
size.Height = location.Y + child.DesiredSize.Height;
location.Offset(0, child.DesiredSize.Height);
}
return size;
}
}
The usage (instead of WrapPanel) is following:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<local:ColumnPanel ViewportHeight="{Binding ViewportHeight, RelativeSource={RelativeSource AncestorType=ScrollViewer}}" ... >
...
</local:ColumnPanel>
</ScrollViewer>
The idea is to calculate layout manually, while MeasureOverride and ArrangeOverride will be automatically called whenever children are changed: added, deleted, resized, etc.
Measure logic is simple: start from (0,0) and measure next child size, if it fits into current column - add it, otherwise start and new column by offsetting location. During whole measurement cycle adjust the resulting size.
The only missing part of puzzle is to provide into measure/arrange cycles ViewportHeight from parent ScrollViewer. This is the role of ColumnPanel.ViewportHeight.
Here is the demo (button add purple circle):
You can do this by wrapping your wrappanel in a scrollviewer, but then binding the height and width of the inner panel to the Height and Width of the Viewport of the scrollviewer, so it stretches and contracts with the rest of the screen.
I've also added minimum Height and Width to my sample, which ensures that the scrollbars appear once the wrap panel is pushed smaller than it's min dimensions
<ScrollViewer x:Name="sv" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<WrapPanel MinWidth="200" Width="{Binding ElementName=sv, Path=ViewportWidth}" MinHeight="200" Height="{Binding ElementName=sv, Path=ViewportHeight}">
<Ellipse Fill="Red" Height="200" Width="200"/>
<Ellipse Fill="Yellow" Height="200" Width="200"/>
<Ellipse Fill="Green" Height="200" Width="200"/>
</WrapPanel>
</ScrollViewer>

Slide in & Slide out animation

I am trying to make a slide in & out animation with c# & WPF.
So far I have managed to code the next lines:
XAML:
<Grid Name="grid" Grid.Column="0" Grid.Row="1">
<Border Name="border" Background="Red"></Border>
</Grid>
c#:
private void Button_Click(object sender, RoutedEventArgs e) {
border.Height = grid.ActualHeight;
if (!isOpen) {
isOpen = true;
DoubleAnimation db = new DoubleAnimation();
db.From = 0;
db.To = grid.ActualHeight;
db.Duration = TimeSpan.FromSeconds(0.5);
border.BeginAnimation(Grid.HeightProperty, db);
} else {
isOpen = false;
DoubleAnimation db = new DoubleAnimation();
db.From = grid.ActualHeight;
db.To = 0;
db.Duration = TimeSpan.FromSeconds(0.5);
border.BeginAnimation(Grid.HeightProperty, db);
}
}
The good thing is that the animation is executed. The bad thing is that this animation has the wrong effect, i mean the animation goes from top to middle and bottom to middle (like if it was shrinking)...
How can i make (or modify in my actual code) the slide effect(top to bottom & bottom to top)?
It has to be in c# code.
You are trying to translate your UI control so use a TranslateTransform (Canvas.Top is possible if you are on a canvas, but inefficient).
Modify your XAML to include a render transform set to a TranslateTransform object:
<Grid Name="grid" Grid.Column="0" Grid.Row="1" ClipToBounds="true">
<Border Name="border" Background="Red">
<Border.RenderTransform>
<TranslateTransform x:Name="borderTransform"/>
</Border.RenderTransform>
</Border>
</Grid>
And animate the Y property of the transform:
DoubleAnimation db = new DoubleAnimation();
db.From = 0;
db.To = grid.ActualHeight;
db.Duration = TimeSpan.FromSeconds(0.5);
borderTransform.BeginAnimation(TranslateTransform.YProperty, db);
Just so you know, doing this is a whole lot cleaner using a Storyboard object (plus you can set it up in XAML!)

Analog Clock, Rotate Hands C# WPF

I am working on a WPF project, trying to create an analog clock. I have an image of a clock (without the hands) and have set it as a background. I also have images of two hands of clock, which I want to rotate keeping one end fixed (like it happens in a clock). How can I rotate the image keeping its one end fixed in C#.NET (WPF)? What I have tried is the following code:
namespace AnalogWatch
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
DispatcherTimer dispatcherTimer;
private int degrees = 0;
public MainWindow()
{
InitializeComponent();
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
degrees += 5;
if (degrees > 360)
{
degrees = 0;
}
RotateTransform transform = new RotateTransform(degrees,StickImg.Width/2,StickImg.Height/2);
//StickImg.RenderTransformOrigin = new System.Windows.Point(0, 0);
StickImg.RenderTransform = transform;
}
private void Window_ContentRendered_1(object sender, EventArgs e)
{
dispatcherTimer.Start();
}
}
}
It is rotating the image but not keeping its one end fixed. What is the problem here ?
XAML is:
<Window x:Class="AnalogWatch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" ContentRendered="Window_ContentRendered_1">
<Grid>
<Image x:Name="StickImg" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Margin="207,70,0,0" Source="stick.png"/>
</Grid>
</Window>
Updated.
Your code should work if clock hand exactly in the center of rectangular image.
Like this one
You can do either like you did
RotateTransform transform =new RotateTransform(degrees, StickImg.Width/2,StickImg.Height/2);
or
RotateTransform transform = new RotateTransform(degrees);
StickImg.RenderTransformOrigin = new Point(0.5, 0.5);
Use the RenderTransformOrigin of the element to set the center of rotation.
Note that the coordinates are scaled to 0..1
So to rotate around the center:
RenderTransformOrigin="0.5, 0.5"
Just make sure that the pivot of the hand is in the center of the element.
here's the code i'm using and it works like charm..as qwr suggested clock hand should be exactly in the center of rectangular image
c# code
DispatcherTimer clock = new DispatcherTimer();
public AnalogClock()
{
InitializeComponent();
clock.Interval =TimeSpan.FromMilliseconds(100);
clock.Tick += clock_Tick;
clock.Start();
}
void clock_Tick(object sender, EventArgs e)
{
double milsec = DateTime.Now.Millisecond;
double sec = DateTime.Now.Second;
double min = DateTime.Now.Minute;
double hr = DateTime.Now.Hour;
seconds.LayoutTransform = new RotateTransform(((sec / 60) * 360)+((milsec/1000)*6));
minutes.LayoutTransform = new RotateTransform((min * 360 / 60)+((sec/60)*6));
hours.LayoutTransform = new RotateTransform((hr * 360 / 12)+(min/2));
}
and the XAML code for images
<Grid Margin="-100">
<Image x:Name="clockface" RenderOptions.BitmapScalingMode="HighQuality" Source="images/panel.PNG" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" VerticalAlignment="Center" Height="194" Margin="100" Width="194"/>
<Image x:Name="hours" RenderOptions.BitmapScalingMode="HighQuality" Source="images/hours.PNG" RenderTransformOrigin="0.5,0.5" VerticalAlignment="Center" HorizontalAlignment="Center" Height="194" Margin="100" Width="194"/>
<Image x:Name="minutes" RenderOptions.BitmapScalingMode="HighQuality" Source="images/minutes.PNG" RenderTransformOrigin="0.5,0.5" VerticalAlignment="Center" HorizontalAlignment="Center" Height="194" Margin="100" Width="194"/>
<Image x:Name="seconds" RenderOptions.BitmapScalingMode="HighQuality" Source="images/seconds.PNG" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" VerticalAlignment="Center" Height="194" Margin="100" Width="194"/>
</Grid>
note the i've used the code in a separate usercontrol..
just make sure that the margin between the clock hand image and the grid i'ts contained in should be enough to make room for the image to rotate else it will displace while rotating..
hope this helps..!

Textbox on canvas drag direction changed after rotated

I have a textblock control on canvas which can be dragged horizontally to the right correctly as shown in the first and second image.
Then after I a 90 degree rotation angle is applied to its CompositeTransform, dragging the textblock to the right actually move it vertically towards the top as shown by the third and fourth image. What am I missing?
public CompositeTransform CurrentTransform = new CompositeTransform();
.....
TextBlock.RenderTransform = CurrentTransform;
....
private double angle;
public double Angle
{
get
{
return angle;
}
set
{
if (angle != value)
{
angle = value;
CurrentTransform.CenterX = 0;
CurrentTransform.CenterY = 0;
CurrentTransform.Rotation = angle;
}
}
}
The moving of the textbox is handled inside
private void CanvasText_ManipulationDelta(object sender, System.Windows.Input.ManipulationDeltaEventArgs e)
{
CurrentTransform.TranslateX += e.DeltaManipulation.Translation.X;
CurrentTransform.TranslateY += e.DeltaManipulation.Translation.Y;
}
For those who are in the same boat, I managed to fix this by attaching external gesture listener from Windows Phone toolkit instead of using the built-in CanvasText_ManipulationDelta event. The textbox dragging works correctly even after rotation.
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Image x:Name="ImageOriginal"
Source="{Binding WbPreview, Mode=TwoWay}"
Stretch="Uniform"/>
<Grid x:Name="GridDraw"
Tap="GridDraw_Tap"
Background="Transparent"/>
<Canvas x:Name="CanvasText">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener Tap="GestureListener_Tap"
DragDelta="GestureListener_DragDelta"/>
</toolkit:GestureService.GestureListener>
</Canvas>
</Grid>

WPF - Turn stackpanel 180° and stay in the same place

So my question is pretty simple:
I have a StackPanel with two objects: a button and an rectangle (which is filled with an image by clicking the button). Now, an additional thing that has to happen is when the button is clicked, the entire stackpanel has to be flipped upside down AND it has to stay in the same place.
I have tried with the RenderTransformOrigin-property set on "0.5,0.5"
but I haven't got any luck with this .. either the stackpanel moved to another location or it disappeared (out of bounds)
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Name="pnlFlip" RenderTransformOrigin="0.5,0.5">
<Button Content="Test" Margin="200,78,197,-78" Name="btnTest" Click="btnTest_Click" Height="30"/>
<Rectangle Margin="175,146,162,-239" Name="rectTest" Fill="Red" Height="127"/>
</StackPanel>
</Grid>
</Window>
The code for my buttons is like this:
private int scale = 1;
private int angle = 180;
private void btnTest_Click(object sender, RoutedEventArgs e)
{
ImageBrush img = new ImageBrush();
img.ImageSource = new BitmapImage(new Uri("pack://application:,,,/WpfApplication1;component/Resources/Images/logo.jpg"));
rectTest.Fill = img;
//Trying a ScaleTranfsformObject
ScaleTransform st = new ScaleTransform();
if(scale == 1)
{
scale = -1;
st.ScaleY = scale;
}
else
{
scale = 1;
st.ScaleY = scale;
}
//Trying a RotateTransform Object
RotateTransform rt = new RotateTransform();
if(angle == 180)
{
rt.Angle = angle;
angle += 180;
}
else
{
rt.Angle = angle;
angle -= 180;
}
pnlFlip.RenderTransform = rt;
}
So what am I doing wrong/how do I fix this?
Using storyboard is better approach than setting this directly in code in click handler. You will get smooth changes in UI.
Following will achieve your objective with XAML only, without any code behind. Setting RenderTransformOrigin in the StackPanel is what keeps it in the center after running the rotate transform.
To activate the animation on click, we just add a Button.Trigger handler which has storyboard on DoubleAnimation to change the angle to 180 degree in the StackPanel. Rest of the XAML is what you had before.
<StackPanel x:Name="pnlFlip" RenderTransformOrigin="0.5,0.5">
<StackPanel.RenderTransform>
<RotateTransform />
</StackPanel.RenderTransform>
<Button Content="Test" Margin="200,78,197,-78" Name="btnTest" Height="30">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="180" Storyboard.TargetName="pnlFlip" Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
<Rectangle Margin="175,146,162,-239" Name="rectTest" Fill="Red" Height="127"/>
</StackPanel>
This works fine on my computer (minus the code for the image in your Resources.)
Here's how it looks when the application launches:
And here's how it looks after I click the button:
Which part of this falls short of your intentions?
Note that you can use a ternary operator to remove your conditional branches and cut down on code, like so:
angle = angle == 180 ? 0 : 180;
rt.Angle = angle;

Categories