WPF Blur effect causing high CPU even when collapsed - wpf

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.

Related

Resizing a form introduces visual artifacts on bottom docked controls

I am working on a "bottom-right docked" popup form.
The layout of the form follows this structure:
Header
Content (text message)
Footer
This form should resize according to its content.
I am using SetBounds to make it grow to the top instead of the bottom (remember that the window is docked bottom-right).
However, when the animation occurs, the footer redraws itself in a very bad way, as its form-relative location is continuously updated.
I provide a sample to give you an idea:
using System.Windows.Forms;
namespace AnimatorTest
{
public class Form3 : Form
{
Timer timer = new Timer();
public Form3()
{
timer.Interval = 30;
timer.Tick += timer_Tick;
// Create 3 test buttons
for (int i = 0; i < 3; i++)
{
Button b = new Button() { Dock = DockStyle.Bottom };
b.Click += (s, e) => timer.Start();
b.Text = "Click and watch how ugly I am during the animation.";
Controls.Add(b);
}
Height = 100;
StartPosition = FormStartPosition.CenterScreen;
}
void timer_Tick(object sender, System.EventArgs e)
{
int desiredHeight = 500;
int difference = desiredHeight - Height;
int change = difference / 6;
if (System.Math.Abs(change) < 1)
{
SetBounds(Left, Top - difference, Width, Height + difference);
timer.Stop();
}
else
{
SetBounds(Left, Top - change, Width, Height + change);
}
}
}
}
I don't really have any idea to work around this.
Thanks.

How to use the slider in WPF to Zoom in and out of an image?

I am trying to use slider to control the Zoom in and Zoom out of any image.
I have written a code:
private void image1_MouseMove(object sender, MouseEventArgs e)
{
if (!image1.IsMouseCaptured) return;
var tt = (TranslateTransform)((TransformGroup)
image1.RenderTransform).Children.First(tr => tr is TranslateTransform);
Vector v = start - e.GetPosition(border1);
tt.X = origin.X - v.X;
tt.Y = origin.Y - v.Y;
}
Here it is working fine. using mouse scroll. But I want to use the slider for the same functioning.
But unable to put the same behaviour like mouse scroll using slider.
I am very new to WPF and its control, so any help with details is highly appreciated.
How can i implement the same finctionality of Zoom in - out using the slider?
After a lot of research and coding I found that its a very simple application and very simple coding too, to Zoomin and Zoomout the image using a slider. what you need is to put a slider in the area of your choice. and put the following code.
private void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
TransformGroup transformGroup = (TransformGroup)image1.RenderTransform;
ScaleTransform transform = (ScaleTransform)transformGroup.Children[0];
double zoom = e.NewValue;
transform.ScaleX = zoom;
transform.ScaleY = zoom;
}
Now I already have the code so that the image will Zoomin and Zoomin using the mousewheel.
private void image1_MouseWheel(object sender, MouseWheelEventArgs e)
{
TransformGroup transformGroup = (TransformGroup)image1.RenderTransform;
ScaleTransform transform = (ScaleTransform)transformGroup.Children[0];
double zoom = e.Delta > 0 ? .2 : -.2;
transform.ScaleX += zoom;
transform.ScaleY += zoom;
slider1.Value += zoom;
}
Now when you put
slider1.value += zoom;
Then changes in the mousewheel and in the changes in the slider change the zoom of the picture.
Remember the difference of implementation between mouse wheel and slider. In mouse wheel MouseWheelEventsArgs "e" and e.Delte value is positive and negative depends on the mousewheel up or mousewheel down. But in Slider RoutedPropertyChangesEventsArgs value is positive or negative according to the sldier up and down. You don't need to specify here. Just
zoom = e.NewValue;
I hope it will help.
You have to code a ScaleTransform in your code. This is a part of my code that do this, I don't put everything but it's a base for you.
For the slider, you have to bind the Zoom property to the Value of the Slider.
private double m_dCurZoom = 1.0;
private ScaleTransform m_transZoom;
public ScaleTransform TransZoom
{
get { return m_transZoom; }
}
private TranslateTransform m_transPan;
public double Zoom
{
get { return m_dCurZoom; }
set
{
double oldzoom = m_dCurZoom;
if (m_dCurZoom != value)
{
m_dCurZoom = value;
OnPropertyChanged("Zoom");
UpdateZoom(oldzoom);
}
}
}
public void UpdateZoom(double oldzoom)
{
// Are we visible?
if (m_root == null)
return;
// Get parent
FrameworkElement parent = GetRootParent();
if (parent == null)
return;
// Center point of the window
Point ptCenter = new Point(parent.RenderSize.Width / 2, parent.RenderSize.Height / 2);
// Translate into canvas coordinates
ptCenter = m_root.TranslatePoint(ptCenter, m_canvas);
// Update the zoom
m_transZoom.ScaleX = m_dCurZoom;
m_transZoom.ScaleY = m_dCurZoom;
m_transPan.X -= ptCenter.X * m_dCurZoom - ptCenter.X * oldzoom;
m_transPan.Y -= ptCenter.Y * m_dCurZoom - ptCenter.Y * oldzoom;
ResizeElementContents();
OnPropertyChanged("Zoom");
}

Bounding box cutting of edges on custom ellipse

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;

Clear pixels in WPF Canvas

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

C# Winform: How to set the Base Color of a TabControl (not the tabpage)

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;
}

Resources