I've been working on developing a custom control which will be used in our CRM frontend. The control itself is nothing special, it simply wraps two labels, text edits, and a button into a single control. (The control is only acting as a wrapper, a bit long winded, but unfortunately our only option due to various restrictions)
I though it would be nice to give the control a Font and ForeColor property, that would change the Font and Color of the labels. Changing the font size means that the relative position of the text boxes be changed to keep everything in line. No problem.
I encapsulated the layout logic in an UpdateLayout method, which is called on the set accessor of the Font property and everything works beautifully at design time, however, at runtime, the Font of the labels is correct, but the layout of the text boxes and button are still in the default positions, hence, the labels overlap.
What am I missing in for updating the position of controls at the init stage in runtime? I've tried calling the UpdateLayout() method from both Initialize and the constructor of the control, alas to no avail.
Am i missing something obvious here?
EDIT:
As requested, I whipped up a quick test. My test control looks like so (Not including Designer code):
public partial class TestControl : UserControl
{
private Font _font;
[Browsable(true)]
public override Font Font
{
get
{
return this._font ?? SystemFonts.DefaultFont;
}
set
{
this._font = value;
this.DoLayout();
}
}
private void DoLayout()
{
this.label1.Font = this._font;
this.Size = new Size(label1.Width + textBox1.Width + 10,
label1.Height >= textBox1.Height ? label1.Height : textBox1.Height);
this.textBox1.Location = new Point(label1.Location.X + 5 + label1.Width, 1);
this.Update();
}
public TestControl()
{
InitializeComponent();
}
protected override void OnLayout(LayoutEventArgs e)
{
base.OnLayout(e);
DoLayout();
}
}
That works great at design time, but runtime, less so...
EDIT2:
So the above code doesn't entirely reflect the problem accurately, however, I have tried Jogy's suggestion of overriding the OnLayout method, and lo and behold, it works!
I'm relatively new to Custom Controls, so a rookie mistake on my part. This will definitely be committed to the long term memory.
Override OnLayout() method and call your UpdateLayout() there.
Thanks for supplying the code, I would provide the properties by reusing already available controls.
public override Font Font
{
get { return this.label1.Font; }
set
{
this.label1.Font = value;
// Additional code to update related controls.
}
}
Also be aware that the declaration of
private Font _font;
Delivers a non-initialized variable, and by using it in the "Do_Layout" might use a null value. Maybe change it to the following when using your code.
this.label1.Font = this.Font;
Related
I've made a pretty slick Windows 8-ish interface using WPF. It already turns out way better than I could wish for, but I was wondering the following:
Is it somehow possible to retrieve the current window colour set by the user? You know, you can set the Aero colour when you right-click the desktop... My plan is to use that colour for a couple of canvas elements on my GUI.
Thanks in advance!
The SystemColours class exists for this very purpose. You can bind directly to it like so
"{DynamicResource {x:Static SystemColors.WindowColorKey}}"
You can query the ColorizationColor registry key for this.
I've even went a step further and created a method to get the hexadecimal colour value, hope this helps you:
public void SomeMethod()
{
int argbColor = (int)Microsoft.Win32.Registry.GetValue(#"HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM","ColorizationColor", null);
var color = System.Drawing.Color.FromArgb(argbColor);
string hexadecimalColor = ConverterToHex(color);
}
private static String ConverterToHex(System.Drawing.Color c)
{
return String.Format("#{0}{1}{2}", c.R.ToString("X2"), c.G.ToString("X2"), c.B.ToString("X2"));
}
I managed to get the correct colour using the following code:
Little sidenote: It has a small correction in it to ignore the alpha bit of the hex number, so I get the full color rather than the less saturated one.
string colorizationValue = string.Format("{0:x}", Microsoft.Win32.Registry.GetValue(#"HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM", "ColorizationColor", "00000000"));
StringBuilder bl = new StringBuilder(colorizationValue);
bl[0] = 'd';
bl[1] = '9';
colorizationValue = bl.ToString();
BrushConverter bc = new BrushConverter();
Brush brush = (Brush)bc.ConvertFrom("#" + colorizationValue);
cvs_barColor.Background = brush;
I created an open-source library for this here which is also available on NuGet.
install-package aerocolor-wpf.AeroColor
After installing the package, you can refer to a DynamicResource called AeroColor and AeroBrush depending on what you need.
There's some setup code that's needed too, but it isn't much. Just put something in your Loaded event handler of the window, as seen below.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
AeroResourceInitializer.Initialize();
}
}
The neat thing about this library is that it installs a hook as well, which updates those resources as the actual Aero color changes in the system too. This means you don't have to handle that either, and if you use a DynamicResource to point to the color in your XAML instead of a StaticResource, WPF will automatically update the color in your UI as well.
Looks very cool when Windows 8 changes the Aero color transitionally and your color follows.
I recently had the need to write a version of the Windows NumericUpDown control which could highlight whether a value was mandatory. It needed to do this by changing the back colour of the control. I thought this would be simple enough, but in trying to do so, I find that it has a wierd side-effect of not quite drawing all the control.
Using the code below, I am seeing this:
When I drop a control on a Windows form, and change the BackColor property (ie. to Blue), the whole of the control's number part changes colour. If, alternatively, I change my "IsMandatory" property, not quite all of the back colour changes (it leaves a border). So, if I change the BackColor to Blue, and then set IsMandatory to True, I get a LightBlue control (the mandatory colour) with a Blue border.
I cannot see why that should be, given that they both use the same code.
Ideas or explanations greatfully received.
public partial class MyNumericUpDown : NumericUpDown
{
private Boolean _isMandatory = false;
private Color _mandatoryBackColor = Color.LightBlue;
private Color _backColor = Color.FromKnownColor(KnownColor.Window);
[DefaultValue(typeof(Color), "Window"), Description("Overridden property")]
override public Color BackColor
{
get { return _backColor; }
set
{
_backColor = value;
MyResetColors();
}
}
[DefaultValue(typeof(Color), "LightBlue"), Category("Appearance")]
public Color MandatoryBackColor
{
get {return _mandatoryBackColor;}
set
{
_mandatoryBackColor = value;
MyResetColors();
}
}
[DefaultValue(false), Category("Behavior")]
public Boolean IsMandatory
{
get { return _isMandatory; }
set
{
_isMandatory = value;
MyResetColors();
}
}
private void MyResetColors()
{
base.BackColor = (this.IsMandatory ? this.MandatoryBackColor : this.BackColor);
}
}
Interesting question, it demonstrates how overriding virtual members can have unexpected side-effects. The core problem is your BackColor property getter, it always returns the _backColor property value, even if you forced it to a different value with IsMandatory. That property getter is also used by Winforms when it needs to draw the control background. So you'll return Blue which explains why you see blue in your screenshot.
But oddly it still works for the text portion of the control. That's because NumericUpdown is made up of multiple controls. You've got a ContainerControl that sets the outer bounds and is the base class, you are overriding its BackColor property. But inside of it are two other controls, a TextBox that displays the text and a Control that displays the up/down buttons. Your BackColor property override does not override their BackColor properties. So the textbox portion will draw with the color you assigned to Base.BackColor
To fix this, you are going to have to stop fibbing about the BackColor. With the extra constraint that you need to make sure that this still works at design time so that the actual BackColor gets serialized and not the MandatoryColor:
[DefaultValue(typeof(Color), "Window"), Description("Overridden property")]
override public Color BackColor {
get {
return base.BackColor;
}
set {
_backColor = value;
MyResetColors();
}
}
private void MyResetColors() {
base.BackColor = this.IsMandatory && !DesignMode ? this.MandatoryBackColor : _backColor;
}
The above method did not work out for me. My workaround was:
private void smartRefresh()
{
if (oldBackColor != BackColor) {
oldBackColor = BackColor;
Hide();
Application.DoEvents();
Show();
Application.DoEvents();
}
}
With a private member oldBackColor.
Now it always shows correctly but does not flicker.
Addendum: I think some part of the Control doesn't get painted at all (I consider it a bug) as the "mispainted" bos around it is not uniformly colored an somtimes traces of the window that was there before can be seen.
Windows does not properly/completely repaint the NumericUpDown control when it is disabled.
See this post: NumericUpDown background colour change for disabled element
Enabling / disabling the control after it is displayed is a work-around.
When I add my UserControls to a FlowLayoutPanel, they display properly. When I change the Dock or Anchor properties on the UserControls before adding them, they are still added but do not render.
According to "How to: Anchor and Dock Child Controls" this should be possible.
I can tell that the controls are added (despite not drawing) because adding enough of them causes a vertical scrollbar to appear.
Setting the "Dock" property of the UserControls to "Left" or "None" will cause them to render, but none of the other options.
Setting the "Anchor" property on the UserControls to anything but Top | Left does not render.
Setting the dock before or after adding the control makes no difference (Add, Dock vs. Dock, Add).
The FlowLayoutPanel is itself is docked (Fill), has FlowDirection set to TopDown, has WrapContents set to false, has AutoScroll set to true, and is otherwise default.
I am using .NET 3.5.
In answer to a comment, the two commented lines are the locations I tried to change the dock. The second spot definitely makes more sense, but I tried the other because it couldn't hurt.
public void CreateObjectControl( object o )
{
ObjectControl oc = new ObjectControl();
oc.MyObject = o;
//This was a spot I mentioned:
//oc.Dock = DockStyle.Fill;
ObjectDictionary.Add( o, oc );
flowLayoutPanel1.Controls.Add( oc );
//This is the other spot I mentioned:
oc.Dock = DockStyle.Fill;
}
try using SuspendLayout and Resumelayout function for the controls before making any amendments which need rendering for proper viewing.
You could see the code from Designer.cs for that particular control
Syntax
control.SuspendLayout();
{Your code for designer amendments}
control.resumeaLayout();
I think I may have found a workaround (read: dirty trick) ... this answer helped to point me in the right direction. Here's an excerpt from the MS article that you also linked to:
For vertical flow directions, the FlowLayoutPanel control calculates the width of an implied column from the widest child control in the column. All other controls in this column with Anchor or Dock properties are aligned or stretched to fit this implied column.
The behavior works in a similar way for horizontal flow directions. The FlowLayoutPanel control calculates the height of an implied row from the tallest child control in the row, and all docked or anchored child controls in this row are aligned or sized to fit the implied row.
This page does not specifically mention that you can't Dock/Anchor the tallest/widest control. But as this control defines the layout behaviour of the FlowLayoutPanel, and thus influences the way all other sibling controls are displayed, it is well possible that Dock and Anchor don't work properly for that 'master control'. Even though I can't find any official documentation regarding that, I believe it to be the case.
So, which options do we have? At runtime, we could add a panel control of height 0 and width of the FlowLayoutPanel client area before you add your usercontrol. You can even set that panel's visibility to false. Subscribing to some Resize/Layout events of the FlowLayoutPanel to keep that panel's size will to the trick. But this does not play nicely at design time. The events won't fire and thus you can't really design the surface the way you want it to look.
I'd prefer a solution that "just works" at design time as well. So, here's an attempt at an "invisible" control that I put together, to fix the controls resizing to zero width if no other control is present. Dropping this as first control onto the FlowLayoutPanel at design time seems to provide the desired effect, and any control subsequently placed on the FlowLayoutPanel is anchorable to the right without shrinking to zero width. The only problem is that, once this invisible control is there, it seems I can't remove it anymore via the IDE. It probably needs some special treatment using a ControlDesigner to achieve that. It can still be removed in the form's designer code though.
This control, once placed onto the FlowLayoutPanel, will listen for resize events of it's parent control, and resize itself according to the ClientSize of the parent control. Use with caution, as this may contain pitfalls that didn't occur to me during the few hours I played with this. For example, I didn't try placing controls that were wider than the FlowLayoutPanel's client area.
As a side note, what will still fail is trying to anchor to the bottom, but that wasn't part of the question ;-)
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Windows.Forms;
namespace ControlTest
{
public sealed class InvisibleControl : Control
{
public InvisibleControl()
{
TabStop = false;
}
#region public interface
// Reduce the temptation ...
public new AnchorStyles Anchor
{
get { return base.Anchor; }
set { base.Anchor = AnchorStyles.None; }
}
public new DockStyle Dock
{
get { return base.Dock; }
set { base.Dock = DockStyle.None; }
}
// We don't ever want to move away from (0,0)
public new Point Location
{
get { return base.Location; }
set { base.Location = Point.Empty; }
}
// Horizontal or vertical orientation?
private Orientation _orientation = Orientation.Horizontal;
[DefaultValue(typeof(Orientation), "Horizontal")]
public Orientation Orientation
{
get { return _orientation; }
set
{
if (_orientation == value) return;
_orientation = value;
ChangeSize();
}
}
#endregion
#region overrides of default behaviour
// We don't want any margin around us
protected override Padding DefaultMargin => Padding.Empty;
// Clean up parent references
protected override void Dispose(bool disposing)
{
if (disposing)
SetParent(null);
base.Dispose(disposing);
}
// This seems to be needed for IDE support, as OnParentChanged does not seem
// to fire if the control is dropped onto a surface for the first time
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
ChangeSize();
}
// Make sure we don't inadvertantly paint anything
protected override void OnPaint(PaintEventArgs e) { }
protected override void OnPaintBackground(PaintEventArgs pevent) { }
// If the parent changes, we need to:
// A) Unsubscribe from the previous parent's Resize event, if applicable
// B) Subscribe to the new parent's Resize event
// C) Resize our control according to the new parent dimensions
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
// Perform A+B
SetParent(Parent);
// Perform C
ChangeSize();
}
// We don't really want to be resized, so deal with it
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
ChangeSize();
}
#endregion
#region private stuff
// Make this a default handler signature with optional params, so that this can
// directly subscribe to the parent resize event, but also be called without parameters
private void ChangeSize(object sender = null, EventArgs e = null)
{
Rectangle client = Parent?.ClientRectangle ?? new Rectangle(0, 0, 10, 10);
Size proposedSize = _orientation == Orientation.Horizontal
? new Size(client.Width, 0)
: new Size(0, client.Height);
if (!Size.Equals(proposedSize)) Size = proposedSize;
}
// Handles reparenting
private Control boundParent;
private void SetParent(Control parent)
{
if (boundParent != null)
boundParent.Resize -= ChangeSize;
boundParent = parent;
if (boundParent != null)
boundParent.Resize += ChangeSize;
}
#endregion
}
}
I would like to resize custom control according to items it content
This dont work for me:
public CustomControl()
{
InitializeComponent();
if (ErrorLimits == false && Range == false)
{
this.Size = new Size(100, 100);
this.Invalidate();
}
else
{
this.Size = new Size(250,250);
this.Invalidate();
}
}
It changing nothing, How can I achieve it?
Thanks!
The containing form will instantiate CustomControl and then set its properties in the form's InitializeComponent function. The property values set in the form's designer are applied after the constructor to CustomControl has finished (which, if you think about it, they'd have to be).
Since you are setting your custom sizes in the control's constructor, they're probably getting overridden by the designer values immediately afterwards before the form is displayed.
A better place to set the size is the UserControl.Load event, which occurs after the designer properties have been set.
An even better option would be to properly support auto sizing.
I've got a StatusStrip with a single ToolStripStatusLabel, Spring=true and a background color for notifications.
The problem is that there's an ugly gray square on the right side of the status strip. After fiddling for a while, I realized this is the sizing grip (I had is set to SizingGrip=false, GripStyle=Hidden). Yet even with it hidden, it still hogs the space. I can't get any content on the status strip to extend all the way to the right.
How would you work around this? Note I can't just set the backcolor of the StatusStrip because the Status Label changes colors and has some fading effects.
The StatusStrip.Padding property is borked, it returns the wrong value for Padding.Right if the sizing grip is disabled. You can fix it in your form constructor, like this:
public Form1() {
InitializeComponent();
statusStrip1.Padding = new Padding(statusStrip1.Padding.Left,
statusStrip1.Padding.Top, statusStrip1.Padding.Left, statusStrip1.Padding.Bottom);
}
Using the Left property to specify Right is the fix. Don't bother submitting this bug to Connect, they won't fix it.
Have a look at this blog entry on MSDN. The question was about changing the size of the sizing grip manually, and I think using the ToolStrip Renderer as suggested could work for you also.
The problem I have so far, is that it removes the background color on a status label in the StatusStrip, so it's not a solution yet, but it's a start.
public MyForm()
{
InitializeComponent();
statusStrip1.Renderer = new MyRenderer();
}
private class MyRenderer : ToolStripProfessionalRenderer
{
protected override void OnRenderStatusStripSizingGrip(ToolStripRenderEventArgs e)
{
// don't draw at all
}
}
I had following problem: when I set tsslSeparator.Spring = true, my right label disappeared immediately after tsslSeparator lost focus. The issue appeared when sizing grip was enabled. When it was disabled, everything worked just fine.
The solution was to set right margin for right label to something different than 0.
tsslLogging.Margin = new Padding(0, 3, 2, 2); // this is necessary for right alignment of status bar label
Hope this helps somebody.
If Microsoft isn't interesting in fixing it, it seems like a proper fix should handle all orientations, and ideally fix all Status Strips (see my answer to Get All Children for definition of GetAllChildren)
public static StatusStrip FixPadding(this StatusStrip ss) {
if (!ss.SizingGrip) {
var fixpad = ss.Padding;
if (ss.Orientation == Orientation.Horizontal) {
if (ss.RightToLeft == RightToLeft.No)
fixpad.Right = fixpad.Left;
else
fixpad.Left = fixpad.Right;
}
else
fixpad.Bottom = fixpad.Top;
ss.Padding = fixpad;
}
return ss;
}
public static void FixStatusStripPadding(this Form f) {
foreach (var ss in f.GetAllChildren().OfType<StatusStrip>())
ss.FixPadding();
}