I have a LayoutTransformControl in which I have a Grid which contains an image and a Canvas. At some point in the application, on button click I am adding some Thumb controls dynamically inside the Canvas to implement drag and drop. Problem is on click of the same button if I set the Angle for the LayoutTransformControl, I would assume it draws all the Thumb controls first and then flip the Layout control as I am creating my Canvas and Thumbs first, but it seems like it is updating the entire layout and the Thumb controls are moving somewhere beyond the Canvas. Is there a way to first render all my Thumbs first and then change the angle so it just rotates the entire control to that angle.
If the angle is 0 i.e. if I don't apply a transform the Thumb controls appears one below the other as below which is fine.
Here is my problem when I say angle is 270, the Thumb controls move away from canvas.
xaml.cs
private void BtnCapture_Click(object sender, RoutedEventArgs e)
{
BarCodeImage.Height = cnvBarCodeImage.Height = MainLayoutControl.Height=480;
BarCodeImage.Width = cnvBarCodeImage.Width = MainLayoutControl.Width;
//This code will create the canvas.
for (int i = 0; i < 2; i++)
{
var item = Selected.WindowLocations[i];
var dimensionsItem = new Dimensions();
dimensions.Add(new Dimensions()
{
Height = 262,
Width = 142,
Left = 395,
Top = 44,
Text = string.Empty,
});
dimensions.Add(new Dimensions()
{
Height = 106,
Width = 147,
Left = 395,
Top = 342,
Text = string.Empty,
}
CreateThumbs(2, dimensions); //This will create the Thumbs and add to the Canvas
RotateImage(270);
}
private void RotateImage(int Angle)
{
MainLayoutControl.Transform = new RotateTransform()
{
Angle = Angle
};
}
private void CreateThumbs(int numberOfWindows, List<Dimensions> dimensions)
{
ClearOrRemoveAllChildren();
Thumb th = null;
for (int i = 0; i < numberOfWindows; i++)
{
th = new Thumb();
th.Name = i.ToString();
var item = dimensions[i];
th.Width = item.Width;
th.Height = item.Height;
th.Foreground = new SolidColorBrush(Windows.UI.Colors.Transparent);
th.BorderBrush = item.BorderColor;
th.BorderThickness = new Thickness(3);
th.Template = GetThumbTemplate(item.Text);
th.DragDelta += (sender, e) => Th_DragDelta(sender, e, dimensions);
th.DragCompleted += (sender, e) => Th_DragCompleted(sender, e, item.IsImageRotated);
Canvas.SetLeft(th, item.Left);
Canvas.SetTop(th, item.Top);
cnvBarCodeImage.Children.Add(th);
}
}
Here is my xaml
<uwpControls:LayoutTransformControl x:Name="MainLayoutControl" Grid.Row="4" HorizontalAlignment="Center" VerticalAlignment="Center" Width="640">
<Grid x:Name="gridBarImagePanel">
<Image x:Name="BarCodeImage"
RenderTransformOrigin="0.5,0.5"></Image>
<Canvas x:Name="cnvBarCodeImage" AllowDrop="True">
</Canvas>
</Grid>
</uwpControls:LayoutTransformControl>
For canvas, child content is not visually clipped if larger than the panel and is not constrained by the bounds of the panel, so it will be such an effect. You set the height of canvas as 480 and width as 640, when you rotate the content of LayoutTransformControl 270 degrees, the height of LayoutTransformControl is still 480 and the width is 640, but the height of canvas becomes 640, the canvas is beyond the scope of LayoutTransformControl, so the thumbs displays like that.
You can set the height and width of canvas and your image both as 480, in that case, no matter how you rotate the canvas you rotate the canvas, it always in the scope of LayoutTransformControl. Then re-layout the two thumbs.
BarCodeImage.Height = cnvBarCodeImage.Height = MainLayoutControl.Height = 480;
BarCodeImage.Width = cnvBarCodeImage.Width = MainLayoutControl.Width = 480;
Or if you still want to keep the width of 640 and the height of 480, you can re-size the LayoutTransformControl after rotating it.
private void RotateImage(int Angle)
{
MainLayoutControl.Transform = new RotateTransform()
{
Angle = Angle
};
MainLayoutControl.Height = 640;
MainLayoutControl.Width = 480;
}
Related
We have to show a legal pop-up in our WPF app. When the pop-up is shown, we use blur effect on the view below.
Recently we recognized that this is causing high GPU usage. Because of the spinner control in the background. The more active content, the more GPU usage.
We collapse this spinner when the pop-up is shown based on a property. But this doesn't help. Only when we set it to collapsed in MainWindow.xaml it works.
We tried multiple things e.g. BitmapCache and other techniques but with no success so far.
Here an example:
https://github.com/rmoergeli/BlurEffectTest.git
I investigated that the problem in demo code hides in your animation: it doesn't stop on changing it visibility from visible to collapsed.
So I found a solution to stop animation on being collapsed with the help of MSDN resource.
public partial class Spinner : UserControl
{
private Canvas _content;
private Storyboard _rotationStoryboard;
public Spinner()
{
// Create a name scope for the page.
NameScope.SetNameScope(this, new NameScope());
DefineContent();
SizeChanged += Spinner_SizeChanged;
IsVisibleChanged += Spinner_IsVisibleChanged;
Loaded += Spinner_Loaded;
}
private void Spinner_Loaded(object sender, RoutedEventArgs e)
{
_rotationStoryboard.Begin(this, isControllable: true);
}
private void Spinner_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool isVisible && isVisible)
_rotationStoryboard.Resume(this);
else
_rotationStoryboard.Pause(this);
}
private void DefineContent()
{
_content = new Canvas();
//set content render transform origin point to center
_content.RenderTransformOrigin = new Point(0.5 , 0.5);
_content.RenderTransform = new RotateTransform(angle: 0);
// Assign the canvas a name by
// registering it with the page, so that
// it can be targeted by storyboard
// animations.
RegisterName("animatableCanvas", _content);
Content = _content;
DefineAnimatableContent();
// Create an animation and a storyboard to animate the
// canvas.
DoubleAnimation doubleAnimation = new DoubleAnimation
{
To = 360,
Duration = new Duration(TimeSpan.FromSeconds(3)),
RepeatBehavior = RepeatBehavior.Forever
};
Storyboard.SetTargetName(doubleAnimation, "animatableCanvas");
Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("RenderTransform.Angle"));
_rotationStoryboard = new Storyboard();
_rotationStoryboard.Children.Add(doubleAnimation);
}
private void DefineAnimatableContent() //renamed Refresh method
{
int n = Balls;
double size = BallSize;
_content.Children.Clear();
double x = ActualWidth / 2;
double y = ActualHeight / 2;
double r = Math.Min(x, y) - size / 2;
double doubleN = Convert.ToDouble(n);
for (int i = 1; i <= n; i++)
{
double doubleI = Convert.ToDouble(i);
double x1 = x + Math.Cos(doubleI / doubleN * 2d * Math.PI) * r - size / 2;
double y1 = y + Math.Sin(doubleI / doubleN * 2d * Math.PI) * r - size / 2;
var e = new Ellipse
{
Fill = BallBrush,
Opacity = doubleI / doubleN,
Height = size,
Width = size
};
Canvas.SetLeft(e, x1);
Canvas.SetTop(e, y1);
_content.Children.Add(e);
};
}
#region Event Handlers
private void Spinner_SizeChanged(object sender, SizeChangedEventArgs e)
{
//we dont need this anymore as we set content render transform origin point to content center
//Transform.CenterX = ActualWidth / 2;
//Transform.CenterY = ActualHeight / 2;
DefineAnimatableContent();
}
#endregion
//other logic
}
This code stops animation on changing Spiner control visibility. But sure you can pause and resume animation with any trigger you need.
N.B! Using this approach you define all the content (not only animarable parts) in code behind so you don't need Spinner.xaml resource anymore.
I'm wondering if there is a way to refresh canvas before adding a child item to it?
I have this code, it draw me a black ellipse with light blue stroke. Now I want to change StrokeThickness when program is launched (I have a slider to define StrokeThickness). The problem is that StrokeThickness is changed but only if I redraw ellipse, but I want that change is made when I move my slider. Any ideas? Thanks!
//this code is in canvas_MouseDown
double smt = sliderThickness.Value;
//krog
elip = new Ellipse
{
Width = 100,
Height = 100,
Fill = Brushes.Black,
Stroke = Brushes.LightBlue,
StrokeThickness = smt,
};
Canvas.SetLeft(elip, mouseX - 50);
Canvas.SetTop(elip, mouseY - 50);
canvas1.Children.Add(elip);
in slider value Changedevent u have to put following code
private void sliderThickness_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
foreach (var item in canvas1.Children)
{
if (item is Ellipse)
{
var elip = item as Ellipse;
elip.StrokeThickness = sliderThickness.Value;
}
}
}
Or shorter
// using System.Linq;
foreach (var ellipse in canvas1.Children.OfType<Ellipse>())
{
ellipse.StrokeThickness = sliderThickness.Value;
}
I have a custom ellipse code shown bellow. I draw a rubber band using the ellipse setting the width and height using two points, code shown bellow. However when I draw the ellipse the bounding box is cutting of the edges on the sides. I solved this issue before with using actual height and width, but this was in a stand alone application. When I integrated it with the rubber band drawing part, actual height and width don't work anymore, for some reason they don't get updated when I set the width and height. Do you know how can I fix it so that the edges don't get cut off.
namespace WpfApplication4
{
class Ellipse2 : Shape
{
EllipseGeometry ellipse;
public static readonly DependencyProperty TextBoxShapeProperty = DependencyProperty.Register("TextBoxShape", typeof(TextBoxShape), typeof(Ellipse2), new FrameworkPropertyMetadata(null));
public TextBoxShape TextBoxShape
{
get { return (TextBoxShape)GetValue(TextBoxShapeProperty); }
set { SetValue(TextBoxShapeProperty, value); }
}
public Ellipse2()
{
ellipse = new EllipseGeometry();
this.Fill = Brushes.Transparent;
this.Stroke = Brushes.Gray;
this.StrokeThickness = 3;
}
protected override Geometry DefiningGeometry
{
get
{
TranslateTransform t = new TranslateTransform(Width / 2, Height / 2);
ellipse.Transform = t;
ellipse.RadiusX = this.Width / 2;
ellipse.RadiusY = this.Height / 2;
return ellipse;
}
}
}
}
double width = Math.Abs(initMousePoint.X - currMousePoint.X);
double height = Math.Abs(initMousePoint.Y - currMousePoint.Y);
double left = Math.Min(initMousePoint.X, currMousePoint.X);
double top = Math.Min(initMousePoint.Y, currMousePoint.Y);
rubberBandShape.Width = width;
rubberBandShape.Height = height;
Canvas.SetTop(rubberBandShape, top);
Canvas.SetLeft(rubberBandShape, left);
Try to compensate for the StrokeThickness, like
ellipse.RadiusX = (this.Width / 2) - StrokeThickness / 2;
ellipse.RadiusY = (this.Height / 2) - StrokeThickness / 2;
I have a transparent canvas on which I can draw arbitrary polylines with the mouse.
Most of the lines are semi-transparent.
Now I need some kind of an eraser tool, i.e. a polyline with an eraser brush, which allows to clear pixels along the mouse movement.
With an opaque canvas I would simply use the background brush but in this case it is Color.FromArgb(0,0,0,0) and drawing with that has no effect.
The canvas seems to be in some kind of alpha blend mode which blends anything I draw on it with what already exists, unless I set the alpha channel to 255 in which case whatever is on the canvas will be overwritten. That does not help me, as I simply want to clear the pixels, i.e. make them fully transparent.
Any ideas?
Here's the main part of the code I'm using:
public class WPFWindow : Window
{
private Canvas canvas = new Canvas();
private bool LDown = false;
private Polyline lines;
private PointCollection points;
public WPFWindow()
{
this.AllowsTransparency = true;
this.WindowStyle = WindowStyle.None;
this.Background = new SolidColorBrush( Color.FromArgb(50,0,0,0) );
this.Width = 500;
this.Height = 400;
this.Top = this.Left = 0;
canvas.Width = this.Width;
canvas.Height = this.Height;
canvas.Background = new SolidColorBrush( Color.FromArgb(0,0,0,0) );
this.Content = canvas;
this.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler( WPFWindow_MouseLeftButtonDown );
this.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler( WPFWindow_MouseLeftButtonUp );
this.MouseMove += new System.Windows.Input.MouseEventHandler( WPFWindow_MouseMove );
}
void WPFWindow_MouseMove( object sender, System.Windows.Input.MouseEventArgs e )
{
if( LDown )
{
points.Add( e.GetPosition(null) );
}
}
void WPFWindow_MouseLeftButtonUp( object sender, System.Windows.Input.MouseButtonEventArgs e )
{
LDown = false;
}
void WPFWindow_MouseLeftButtonDown( object sender, System.Windows.Input.MouseButtonEventArgs e )
{
LDown = true;
lines = new Polyline();
points = new PointCollection();
lines.Stroke = new SolidColorBrush( Color.FromArgb( 128, 180, 80, 80 ) );
lines.StrokeThickness = 20;
lines.Points = points;
points.Add( e.GetPosition(null) );
canvas.Children.Add( lines );
}
}
The WPF Canvas control is not a drawing surface, it's a "Panel", a container that arranges multiple other controls.
Each Polyline you add to the Canvas is actually a FrameworkElement (a kind of lightweight control) and they are all drawn in order (it's like adding multiple labels or edit controls, there is no way a control can change the visual representation of another control on the window except for covering it up).
You may want to create an actual image draw the polylines on the image and display that image, then you can talk about clearing pixels.
Use an InkCanvas instead of polylines. It has an eraser already implemented
It seems like a simple question but how do I set the bacground color of the 'tab control', it seems to be derived from the standard window theme color. Is it Possible to create a black tab control with white text written on the tabs themselves (not the tab page)?
Help, I,m a little familiar with custom controls extending existing controls but I don't know what properties (if they exist) to set.
http://dotnetrix.co.uk/tabcontrol.htm
private void tabControl1_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
TabPage CurrentTab = tabControl1.TabPages[e.Index];
Rectangle ItemRect = tabControl1.GetTabRect(e.Index);
SolidBrush FillBrush = new SolidBrush(Color.Red);
SolidBrush TextBrush = new SolidBrush(Color.White);
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
//If we are currently painting the Selected TabItem we'll
//change the brush colors and inflate the rectangle.
if (System.Convert.ToBoolean(e.State & DrawItemState.Selected))
{
FillBrush.Color = Color.White;
TextBrush.Color = Color.Red;
ItemRect.Inflate(2, 2);
}
//Set up rotation for left and right aligned tabs
if (tabControl1.Alignment == TabAlignment.Left || tabControl1.Alignment == TabAlignment.Right)
{
float RotateAngle = 90;
if (tabControl1.Alignment == TabAlignment.Left)
RotateAngle = 270;
PointF cp = new PointF(ItemRect.Left + (ItemRect.Width / 2), ItemRect.Top + (ItemRect.Height / 2));
e.Graphics.TranslateTransform(cp.X, cp.Y);
e.Graphics.RotateTransform(RotateAngle);
ItemRect = new Rectangle(-(ItemRect.Height / 2), -(ItemRect.Width / 2), ItemRect.Height, ItemRect.Width);
}
//Next we'll paint the TabItem with our Fill Brush
e.Graphics.FillRectangle(FillBrush, ItemRect);
//Now draw the text.
e.Graphics.DrawString(CurrentTab.Text, e.Font, TextBrush, (RectangleF)ItemRect, sf);
//Reset any Graphics rotation
e.Graphics.ResetTransform();
//Finally, we should Dispose of our brushes.
FillBrush.Dispose();
TextBrush.Dispose();
}
I use something like this in mu TabControl derived class (and it will do gradients too):
protected override void OnDrawItem(DrawItemEventArgs e)
{
// fill in the whole rect
using (SolidBrush br = new SolidBrush(Theme.FormBackColor))
{
e.Graphics.FillRectangle(br, ClientRectangle);
}
// draw the tabs
for (int i = 0; i < TabPages.Count; ++i)
{
TabPage tab = TabPages[i];
// Get the text area of the current tab
RectangleF tabTextArea = (RectangleF)GetTabRect(i);
// determine how to draw the tab based on which type of tab it is
Color tabTopBackColor = GetTopBackColor();
Color tabBottomBackColor = GetBottomBackColor();
Color tabTextColor = GetTextColor();
// draw the background
using (LinearGradientBrush br = new LinearGradientBrush(tabTextArea, tabTopBackColor, tabBottomBackColor, LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(br, tabTextArea);
}
// draw the tab header text
using (SolidBrush brush = new SolidBrush(tabTextColor))
{
e.Graphics.DrawString(tab.Text, Font, brush, CreateTabHeaderTextRect(tabTextArea));
}
}
}
private RectangleF CreateTabHeaderTextRect(RectangleF tabTextArea)
{
tabTextArea.X += 3;
tabTextArea.Y += 1;
tabTextArea.Height -= 1;
return tabTextArea;
}