Resizing a form introduces visual artifacts on bottom docked controls - winforms

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.

Related

WPF Blur effect causing high CPU even when collapsed

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.

How to duplicate a custom control (panel) programmatically

I am using C# in VS 2010. I created a custom panel and would like to add this custom panel 9 times so I created a loop to add a copy of the panel 9 times at equal distance from each other. Each panel will have its own text and image. All I'm getting though is a single panel. Any insight would be appreciated
public partial class Form1 : Form
{
int index = 0;
List<CutePanel.CustomPanel> MenuItems = new List<CutePanel.CustomPanel>();
public Form1()
{
InitializeComponent();
for (int i = 0; i < 9; i++)
{
this.cpTest.BackColor = System.Drawing.SystemColors.ActiveCaption;
this.cpTest.LabelText = "My super click text";
this.cpTest.Location = new System.Drawing.Point(12, 12+(64*i));
this.cpTest.Name = "cpTest";
this.cpTest.Size = new System.Drawing.Size(344, 58);
this.cpTest.SuperClick = null;
this.cpTest.TabIndex = 6;
}
cpTest.MouseClick += new MouseEventHandler(cpTest_MouseClick);
cpTest.SuperClick += new EventHandler(cpTest_SuperClick);
cpTest.LabelText = "This is my text.";
MenuItems.Add(cpTest);
}
void cpTest_SuperClick(object sender, EventArgs e)
{
tcTest.SelectedIndex = index++ % 2;
}
void cpTest_MouseClick(object sender, MouseEventArgs e)
{
tcTest.SelectedIndex = index++ % 2;
}
private void customPanel3_MouseClick(object sender, MouseEventArgs e)
{
tcTest.SelectedIndex = index++ % 2;
}
}
Thanks.
You have to make a distinction between your panel class and panel objects, also called instances of this class. Think of the class as a template that serves in creating objects. These objects are created with the new keyword:
for (int i = 0; i < 9; i++)
{
var cp = new CutePanel.CustomPanel();
cp.BackColor = System.Drawing.SystemColors.ActiveCaption;
cp.LabelText = "My super click text";
cp.Location = new System.Drawing.Point(12, 12+(64*i));
cp.Name = "cpTest" + i;
cp.Size = new System.Drawing.Size(344, 58);
cp.SuperClick = null;
cp.TabIndex = 6;
cp.MouseClick += new MouseEventHandler(cpTest_MouseClick);
cp.SuperClick += new EventHandler(cpTest_SuperClick);
cp.LabelText = "This is my text.";
MenuItems.Add(cp);
}
You can also assign it values from the existing panel:
cp.BackColor = cpTest.BackColor;
cp.Size = cpTest.Size;
...
An elegant way of making a duplicate is to include a Clone method in your panel class
public class CustomPanel
{
...
public CustomPanel Clone()
{
var cp = (CustomPanel)this.MemberwiseClone();
cp.Parent = null; // It has not yet been added to the form.
return cp;
}
}
Then
for (int i = 0; i < 9; i++)
{
CustomPanel cp = cpTest.Clone();
// Now only change the differing properties
cp.Location = new System.Drawing.Point(12, 12+(64*i));
cp.Name = "cpTest" + i;
cp.TabIndex += i + 1;
MenuItems.Add(cp);
}
but attention: If the cloned control is a container control containing other controls, you must clones those recursively as well. I.e., you must perform a deep clone! Creating new controls as shown in my first code snippet is safer.

FocusVisualStyle In RadioButton not Work

This is not working for me, focus on radio button only works when pressed the Tab key! Does anyone know how to fix?
void SelectPaymentModeView_Loaded(object sender, RoutedEventArgs e)
{
this.radPaymentMode.Focus();
}
The contents of the radiobutton is text... I also try Keyboard.Focus(this.radPaymentMode);
See the complete code:
PaymentMode[] modes = data[1] as PaymentMode[];
if (modes.Length > 0)
{
for (int i = 0; i < modes.Length; i++)
{
RadioButton rad = new RadioButton();
rad.Name = "radPayment" + i;
rad.GroupName = "PaymentModes";
rad.Content = modes[i].Name;
rad.DataContext = modes[i];
rad.Margin = new Thickness(110, 0, 0, 5);
rad.VerticalAlignment = System.Windows.VerticalAlignment.Center;
rad.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
Grid.SetRow(rad, 3 + i);
Grid.SetColumn(rad, 1);
gridPaymentModes.RowDefinitions.Insert(3, new RowDefinition());
gridPaymentModes.Children.Add(rad);
radPaymentModes.Add(rad);
if (!string.IsNullOrEmpty((this.DataContext as Order).Payment))
{
String paymentOrder = rad.Content as String;
if (paymentOrder.Equals((this.DataContext as Order).Payment))
{
rad.IsChecked = true;
}
}
rad.Checked += new RoutedEventHandler(rad_Checked);
}
radPaymentModes[0].Loaded += SelectPaymentModeView_Loaded;
}
void SelectPaymentModeView_Loaded(object sender, RoutedEventArgs e)
{
FocusManager.SetFocusedElement(FocusManager.GetFocusScope((sender as RadioButton)), (sender as RadioButton));
}
The keyboard focus manager makes the dotted focus adorner visible when the keyboard is used to tab to a control (WPF wants to hide the focus rect when the mouse is used for example so there's less visual clutter).
To force it, use code like this (assuming btnRadio is your button):
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(btnRadio), btnRadio);

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

Add WPF control at runtime

I've written a WPF UserControl, and want to add one or more of it to my Window at runtime when I click a button. How can I do that?
Edit: Further specification
I want to add the usercontrols to a Canvas, and put in a absolute position. The canvas is a drawing of the floors in my house, and each usercontrol has properties to indicate where in the house it is positioned. So I want all the controls to be positioned in the correct position on the canvas.
I'm thinking something like this
var light = new LightUserControl(2);
HouseCanvas.Children.Add(light); // this should be positioned in a specific place
After you add the your control to the Canvas you need to specify the top and left co-ordinates using the Canvas.Top and Canvas.Left attached properties as follows.
var light = new LightUserControl(2);
HouseCanvas.Children.Add(light);
Canvas.SetLeft(light, 20);
Canvas.SetTop(light, 20);
In case you want to add the control to a Grid instead of a Canvas you can specify all the Grid properties through the Grid static class as follows:
Label newLabel = new Label();
newLabel.Content = "The New Element";
Main.Children.Add(newLabel);
Grid.SetColumn(newLabel, 0);
Grid.SetRow(newLabel, 0);
Add a StackPanel to the window and on each button click,
_stackPanel.Children.Add(new YourControl());
You can do this in many ways.
My solution:
for (i = 1; i <= buttoncount; i++)
{
Button mybutton = new Button();
Grid1.Children.Add(mybutton);
mybutton.Height = 100;
mybutton.Width = 100;
mybutton.Name = "button" + i;
mybutton.Content = mybutton.Name;
}
public static void AddChild(this Visual parent, UIElement child)
{
if (InternalAddChild(parent, child))
{
return;
}
throw new NotSupportedException();
}
private static bool InternalAddChild(Visual parent, UIElement child)
{
Panel panel = parent as Panel;
if (panel != null)
{
panel.Children.Add(child);
return true;
}
for (int i = VisualTreeHelper.GetChildrenCount(parent) - 1; i != -1; i--)
{
Visual target = VisualTreeHelper.GetChild(parent, i) as Visual;
if (target != null && InternalAddChild(target, child))
{
return true;
}
}
return false;
}
Just complementing the answer:
for (i = 1; i <= buttoncount; i++)
{
Button mybutton = new Button();
Grid1.Children.Add(mybutton);
mybutton.Height = 100;
mybutton.Width = 100;
mybutton.Name = "button" + i;
mybutton.Content = mybutton.Name;
mybutton.Click += button_Click;
}
private void button_Click(object sender, RoutedEventArgs e)
{
// do something
}

Resources