C# best way to "validate" an object bound to a form - c#

I got a business object bounds to a form (each property is bound to a control). There is some business specificatios (such as this field should not be empty, this one must be greater than 0 etc...). What is the best way to check all the rules ?
I currently have a validator on each contorl, so I can check for all validator to be ok, but I don't really like this solution. Indeed the rules are dispached and it's no easy to see all at once.
I can have a big method CheckValidaty that check for all the rules but this leads to a double check with the validators.
What would you do, other solution ?

I would suggest that let the BusinessObject implement IDataErrorInfo. I think it is the cleanest way to handle business errors.
Take a look at these links:
http://msdn.microsoft.com/en-us/library/system.componentmodel.idataerrorinfo_members.aspx
http://www.codegod.de/WebAppCodeGod/objectdatasource-and-idataerrorinfo-with-winforms-AID427.aspx

There are two kinds of validation: data-specific validation (in the persistence layer) and user-interface validation. I prefer to put validation near the input side, because generally you want to show the user what's wrong, and trying to connect data validation to the user interface adds more indirection that has to match the data binding indirection.
Putting data validation in control classes does not seem like a good idea. That basically means the control class can be used only for one specific field.
The standard Windows Forms way of doing things is to put data validation in container. That way the validation can check against the state of other properties and connect the specific control to the ErrorProvider object(s) to display a pertinent error message.
class EmployeeForm : UserControl
{
EmployeeObject employee;
// ...
void employeeNameTextBox_Validating (object sender, CancelEventArgs e)
{
if ( employee.Name.Trim ().Length == 0 ) {
errorProvider.SetError (employeeNameTextBox, "Employee must have a name");
e.Cancel = true;
}
}
void employeeHireDateControl_Validating (...)
{
if ( employee.HireDate < employee.BirthDate ) {
errorProvider.SetError (employeeHireDateControl,
"Employee hire date must be after birth date");
e.Cancel = true;
}
}
}
class ExplorerStyleInterface : ...
{
// ...
bool TryDisplayNewForm (Form oldForm, Form newForm)
{
if ( ! oldForm.ValidateChildren () )
return false;
else {
HideForm (oldForm);
ShowForm (newForm);
return true;
}
}
}
The standard WF way is to fire the Validating event for the specific control when the control loses focus or when ValidateChildren is called on the container (or the container's container). You set up a handler for this event through the event properties for the control on the container; the handler is automatically added to the container.
I'm not sure why this way isn't working for you, unless you don't like the default behavior of refusing to reliquish focus on error, which you can change by setting the AutoValidate property of the container (or the container's container) to EnableAllowFocusChange.
Tell us specifically what you don't like about the standard Windows Forms way of doing things, and maybe we can either offer alternatives or persuade you the standard way will do what you want.

If you have this situation:
- Many controls in the Winform to
validate
- A validation rule for each control
- You want an overall validation within the Save() command
- You don't want validation when controls focus changes
- You also need an red icon showing errors in each control
Then you can copy and paste the following code, that implements a Form with 2 textbox and a Save button:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace ValidationApp
{
public class ValidationTestForm : Form
{
private TextBox textBox1;
private TextBox textBox2;
private Button btnSave;
private ErrorProvider errorProvider1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.textBox1 = new System.Windows.Forms.TextBox();
this.textBox2 = new System.Windows.Forms.TextBox();
this.btnSave = new System.Windows.Forms.Button();
this.errorProvider1 = new System.Windows.Forms.ErrorProvider(this.components);
((System.ComponentModel.ISupportInitialize)(this.errorProvider1)).BeginInit();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(131, 28);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(100, 20);
this.textBox1.TabIndex = 0;
//
// textBox2
//
this.textBox2.Location = new System.Drawing.Point(131, 65);
this.textBox2.Name = "textBox2";
this.textBox2.Size = new System.Drawing.Size(100, 20);
this.textBox2.TabIndex = 1;
//
// btnSave
//
this.btnSave.Location = new System.Drawing.Point(76, 102);
this.btnSave.Name = "btnSave";
this.btnSave.Size = new System.Drawing.Size(95, 30);
this.btnSave.TabIndex = 2;
this.btnSave.Text = "Save";
this.btnSave.UseVisualStyleBackColor = true;
this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
//
// errorProvider1
//
this.errorProvider1.ContainerControl = this;
//
// ValidationTestForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(266, 144);
this.Controls.Add(this.btnSave);
this.Controls.Add(this.textBox2);
this.Controls.Add(this.textBox1);
this.Name = "ValidationTestForm";
this.Text = "ValidationTestForm";
((System.ComponentModel.ISupportInitialize)(this.errorProvider1)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
public ValidationTestForm()
{
InitializeComponent();
// path validation
this.AutoValidate = AutoValidate.Disable; // validation to happen only when you call ValidateChildren, not when change focus
this.textBox1.CausesValidation = true;
this.textBox2.CausesValidation = true;
textBox1.Validating += new System.ComponentModel.CancelEventHandler(textBox1_Validating);
textBox2.Validating += new System.ComponentModel.CancelEventHandler(textBox2_Validating);
}
private void textBox1_Validating(object sender, System.ComponentModel.CancelEventArgs e)
{
if (textBox1.Text.Length == 0)
{
e.Cancel = true;
errorProvider1.SetError(this.textBox1, "A value is required.");
}
else
{
e.Cancel = false;
this.errorProvider1.SetError(this.textBox1, "");
}
}
private void textBox2_Validating(object sender, System.ComponentModel.CancelEventArgs e)
{
if (textBox2.Text.Length == 0)
{
e.Cancel = true;
errorProvider1.SetError(this.textBox2, "A value is required.");
}
else
{
e.Cancel = false;
this.errorProvider1.SetError(this.textBox2, "");
}
}
private void btnSave_Click(object sender, EventArgs e)
{
if (this.ValidateChildren()) //will examine all the children of the current control, causing the Validating event to occur on a control
{
// Validated! - Do something then
}
}
}
}
Note:
textBox1_Validating, textBox2_Validating...do the rule check

What is wrong with the validator approach? It's quite acceptable, and you can write your own, to implement your own rules.

Related

Refactor code to use Custom class instead of the one the System provides

Well, this is probably the dumbest question yet, but I have a huge problem that bothers me. First off, I used the code sample from Mick Doherty's Tab Control Tips under reposition TabItems at runtime to allow drag and drop of my tabs in my control. The problem is I use a custom TabPage class by the name of ExtendedTabPage and this causes me trouble. I tried casting or using the as keyword but I am out of luck, so I would like someone to help me out on how to refactor the code to allow for dragging of the custom tabs.
EDIT: I forgot to mention that ExtendedTabPage is an abstract class that is inherited by my objects (for example one of my Windows belongs to ConsoleTab class that inherits ExtendedTabPage). Is this any relevant to the problem itself?
EDIT 2: Major discovery - In the DragOver method, if I try using ConsoleTab in the typeof statements, it seems to work perfectly fine. The problem is that I do not want to do this specifically for this class but for all classes inheriting from its parent class, which is abstract (and I can actually convert it to non-abstract if need be but I won't actively use it...).
EDIT 3: Maybe a good way would be to directly swap using the indexes and avoid using the TabPage Data, however I am a bit confused on how to do this...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ReCodeConsole.Properties;
namespace ReCodeConsole.Core
{
/// <summary>
/// Implements all the extra functionality needed for tab pages on top of the existing TabControl.
/// Includes events for DrawItem, MouseMove and MouseDown.
/// </summary>
public partial class ExtendedTabControl : TabControl
{
/// <summary>
/// Initializes a new instance of the ExtendedTabControl class. All events are added.
/// </summary>
public ExtendedTabControl() :base()
{
this.DrawItem+=new DrawItemEventHandler(DrawTab);
this.MouseClick+=new MouseEventHandler(Tab_OnMouseDown);
this.MouseMove+=new MouseEventHandler(Tab_OnMouseMove);
this.DragOver+=new DragEventHandler(Tab_OnDragOver);
}
/// <summary>
/// Used to store the starting position of a tab drag event.
/// </summary>
private Point DragStartPosition = Point.Empty;
private void DrawTab(object sender, DrawItemEventArgs e)
{
//
//This code will render the close button at the end of the Tab caption.
//
e.Graphics.DrawImage(Resources.TabCloseButton, e.Bounds.Right - 22, e.Bounds.Top + 5, 14, 14);
e.Graphics.DrawString(this.TabPages[e.Index].Text, e.Font, Brushes.Black, e.Bounds.Left + 12, e.Bounds.Top + 3);
e.DrawFocusRectangle();
}
private void Tab_OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
//
// Regardless of where the MouseDown event originated, save the coordinates for dragging.
//
DragStartPosition = new Point(e.X, e.Y);
#region Close Button Handling
//
// Close button code - looping through the controls.
//
for (int i = 0; i < this.TabPages.Count; i++)
{
Rectangle r = GetTabRect(i);
//
//Getting the position of the close button.
//
Rectangle closeButton = new Rectangle(r.Right - 22, r.Top + 5, 14, 14);
if (closeButton.Contains(e.Location))
{
if (this.TabPages[i] is ExtendedTabPage)
{
if ((this.TabPages[i] as ExtendedTabPage).IsCloseable)
{
if (MessageBox.Show("Are you sure you want to close this tab?", "Close", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
this.TabPages.RemoveAt(i);
break;
}
}
}
else
{
if (MessageBox.Show("Are you sure you want to close this tab?", "Close", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
this.TabPages.RemoveAt(i);
break;
}
}
}
}
#endregion
}
private TabPage HoverTab()
{
for (int index = 0; index <= TabCount - 1; index++)
{
if (GetTabRect(index).Contains(PointToClient(Cursor.Position)))
return (TabPage)TabPages[index];
}
return null;
}
private void Tab_OnDragOver(object sender, System.Windows.Forms.DragEventArgs e)
{
TabPage hover_Tab = HoverTab();
if (hover_Tab == null)
e.Effect = DragDropEffects.None;
else
{
if (e.Data.GetDataPresent(typeof(TabPage)))
{
e.Effect = DragDropEffects.Move;
TabPage drag_tab = (TabPage)e.Data.GetData(typeof(TabPage));
if (hover_Tab == drag_tab) return;
Rectangle TabRect = GetTabRect(TabPages.IndexOf(hover_Tab));
TabRect.Inflate(-3, -3);
if (TabRect.Contains(PointToClient(new Point(e.X, e.Y))))
{
SwapTabPages(drag_tab, hover_Tab);
SelectedTab = drag_tab;
}
}
}
}
private void Tab_OnMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
Rectangle r = new Rectangle(DragStartPosition, Size.Empty);
r.Inflate(SystemInformation.DragSize);
TabPage tp = HoverTab();
if (tp != null)
{
if (!r.Contains(e.X, e.Y))
DoDragDrop(tp, DragDropEffects.All);
}
DragStartPosition = Point.Empty;
}
private void SwapTabPages(TabPage tp1, TabPage tp2)
{
int Index1 = this.TabPages.IndexOf(tp1);
int Index2 = this.TabPages.IndexOf(tp2);
this.TabPages[Index1] = tp2;
this.TabPages[Index2] = tp1;
}
}
}
Now ignore anything that is extra, I give you the whole code in case anything else messes it all up. So to recap, I want someone to fix the code or at least explain how to do so (or if it isn't possible what to do) in order for me to be able to swap between ExtendedTabPage items instead of regular TabPage. I have tried and regular TabPage objects work, whereas my custom ones don't. If the solution has to include only one of them (so normal TabPages cannot work), go for only ExtendedTabPage as I can convert the rest of the stuff into that. I hope this is not like really dumb and very much a situation where I am overlooking something.
P.S: I also checked the page I linked for a solution that worked for custom classes, but with no luck, as it caused me twice the problems and half my code broke even with the proper assembly references, so that is not a real option. :/
Well, as nobody seems to know the solution and after excessive testing and tweaking plus some very lucky finds in the site, namely How to deal with GetDataPresent to let it accept all the derived types and C# Drag and Drop - e.Data.GetData using a base class, I am happy to report that the problem can easily enough be solved by substituting the GetPresentData and GetData calls. I provide the tweaked code for Tab_OnDragOver which was where the problem originated and I hope it all works fine for anyone who hits this page and looks for a solution!
private void Tab_OnDragOver(object sender, System.Windows.Forms.DragEventArgs e)
{
TabPage hover_Tab = HoverTab();
if (hover_Tab == null)
e.Effect = DragDropEffects.None;
else
{
var drag_tab = e.Data.GetData(e.Data.GetFormats()[0]);
if (typeof(TabPage).IsAssignableFrom(drag_tab.GetType()))
{
e.Effect = DragDropEffects.Move;
if (hover_Tab == drag_tab) return;
Rectangle TabRect = GetTabRect(TabPages.IndexOf(hover_Tab));
TabRect.Inflate(-3, -3);
if (TabRect.Contains(PointToClient(new Point(e.X, e.Y))))
{
SwapTabPages(drag_tab as TabPage, hover_Tab);
SelectedTab = drag_tab as TabPage;
}
}
}
}

C# winforms possible memoryleak when adding and removing custom controls to flowlayoutpanel (many User objects in taskmanager)

I have a medium-sized Winforms application (dotNET4.0) on which I'm dynamically adding and removing custom controls from a flowlayoutpanel.
Based on user selections, the amount of these controls vary.
This is working fine, except that I notice some memoryleaks. I check this by monitoring the 'USER Objects' number in Taskmanager. The number goes up when I'm adding the custom controls to the flowlayoutpanel but doesn't go all the way down again when disposing these.
Actually: the number of USER Objects goes down a lot (lets say from 100% to 10%: so 90% is properly disposed, leaving me with 10% in memory)...
Concluding: one or more objects still remain in memory after disposing the user control.
My guess is the delegates or the images, but I'm clueless... Could it be something from the static?
So my actual question is: Where am I not properly freeing memory from the user controls and how can I resolve this?
Could you guys please assist?
Many many thanks in advance!
This is my user control:
Note that everything can be disposed EXCEPT the _Costlinereportdata object.
(this should remain in use because it is linked and used in other parts)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Classes.CustomControls
{
public partial class CostLineReport : UserControl, IDisposable
{
#region CONSTANTS
public static int pnlWidth = 870;
public static int pnlHeightBase = 112;
public static int pnlHeightExtended = 415;
public static int pnlHeightCollapsed = 30;
public static Color ColorNeutral = Color.FromArgb(176, 196, 222);
public static Color ColorApprove = Color.FromArgb(173, 255, 47);
public static Color ColorDisapprove = Color.FromArgb(255, 99, 71);
public static Image ImageApprove = Image.FromFile(#"Images\tableAdd.png");
public static Image ImageDisapprove = Image.FromFile(#"Images\tableDelete.png");
public static Image ImageDetail = Image.FromFile(#"Images\tableDetail.png");
public static Image ImageCollapse = Image.FromFile(#"Images\compile-warning.png");
public static Image ImageViewRecords = Image.FromFile(#"Images\table.png");
public static Image ImageCalculationInclude = Image.FromFile(#"Images\add.png");
public static Image ImageCalculationExclude = Image.FromFile(#"Images\delete.png");
#endregion
#region FIELDS
private CostLineReportData _Costlinereportdata;
private ToolTip ttpApprove;
private ToolTip ttpDisapprove;
private ToolTip ttpCollapse;
private ToolTip ttpDetail;
private ToolTip ttpViewRecords;
private ToolTip ttpCalculation;
#endregion
#region CTORS
public CostLineReport(CostLineReportData line)
{
InitializeComponent();
//
this._Costlinereportdata = line;
//
this.picApprove.Click += new EventHandler(Approve);
this.picDisapprove.Click += new EventHandler(Disapprove);
this.picDetail.Click += new EventHandler(ResizeControl);
this.picCollapse.Click += new EventHandler(CollapseControl);
this.picViewRecords.Click += new EventHandler(ShowRecords);
this.picCalculation.Click += new EventHandler(SwitchCalculateState);
//
this.rtMainData.Text = _Costlinereportdata.Maintext;
this.rtDetail.Text = _Costlinereportdata.Detailtext; ;
this.lblTitle.Text = _Costlinereportdata.Title;
//
ttpApprove = new ToolTip();
ttpDisapprove = new ToolTip();
ttpCollapse = new ToolTip();
ttpDetail = new ToolTip();
ttpViewRecords = new ToolTip();
ttpCalculation = new ToolTip();
ttpApprove.SetToolTip(this.picApprove, "Approve this line");
ttpDisapprove.SetToolTip(this.picDisapprove, "Disapprove this line");
ttpCollapse.SetToolTip(this.picCollapse, "Collapse this line");
ttpDetail.SetToolTip(this.picDetail, "Show detail");
ttpViewRecords.SetToolTip(this.picViewRecords, "View associated recordset");
ttpCalculation.SetToolTip(this.picCalculation, "Include/Exclude from calculation");
//
this.picApprove.Image = CostLineReport.ImageApprove;
this.picDisapprove.Image = CostLineReport.ImageDisapprove;
this.picDetail.Image = CostLineReport.ImageDetail;
this.picCollapse.Image = CostLineReport.ImageCollapse;
this.picViewRecords.Image = CostLineReport.ImageViewRecords;
this.picCalculation.Image = CostLineReport.ImageCalculationExclude;
//
Recolor();
}
#endregion
#region PROPERTIES
public RichTextBox MainTextBox
{ get { return this.rtMainData; } }
public RichTextBox DetailTextBox
{ get { return this.rtDetail; } }
public Label TitleLabel
{ get { return this.lblTitle; } }
public PictureBox CalculateControl
{ get { return this.picCalculation; } }
#endregion
#region METHODS
private void Approve(object o, EventArgs e)
{
_Costlinereportdata.Approve();
Recolor();
}
private void Disapprove(object o, EventArgs e)
{
_Costlinereportdata.Disapprove();
Recolor();
}
private void ResizeControl(object o, EventArgs e)
{
_Costlinereportdata.SwitchSize();
switch(_Costlinereportdata.Viewstate)
{
case ViewState.Base:
this.Height = CostLineReport.pnlHeightBase;
break;
case ViewState.Extended:
this.Height = CostLineReport.pnlHeightExtended;
break;
}
}
private void CollapseControl(object o, EventArgs e)
{
_Costlinereportdata.Collapse();
if (_Costlinereportdata.Collapsed)
this.Height = CostLineReport.pnlHeightCollapsed;
else
this.Height = CostLineReport.pnlHeightBase;
}
private void Recolor()
{
switch (_Costlinereportdata.Approvalstate)
{
case ApprovalState.Approved:
foreach (Control c in pnlColorIndicator.Controls)
{
if (c is PictureBox)
((PictureBox)c).BackColor = CostLineReport.ColorApprove;
}
pnlColorIndicator.BackColor = CostLineReport.ColorApprove;
break;
case ApprovalState.Disapproved:
foreach (Control c in pnlColorIndicator.Controls)
{
if (c is PictureBox)
((PictureBox)c).BackColor = CostLineReport.ColorDisapprove;
}
pnlColorIndicator.BackColor = CostLineReport.ColorDisapprove;
break;
case ApprovalState.Neutral:
foreach (Control c in pnlColorIndicator.Controls)
{
if (c is PictureBox)
((PictureBox)c).BackColor = CostLineReport.ColorNeutral;
}
pnlColorIndicator.BackColor = CostLineReport.ColorNeutral;
break;
}
}
private void ShowRecords(object sender, EventArgs e)
{
if (this._Costlinereportdata.Costline.LocalData != null)
{
using (Forms.frmCostlineRecords f = new Forms.frmCostlineRecords(this._Costlinereportdata.Costline.LocalData))
{
f.ShowDialog();
}
}
else
MessageBox.Show("This line has no records associated to it. The detailed list cannot be shown.",
"Can't show form",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
private void SwitchCalculateState(object sender, EventArgs e)
{
if (this._Costlinereportdata.Calculationstate == CalculationState.Included)
{
this._Costlinereportdata.Calculationstate = CalculationState.Excluded;
this.picCalculation.Image = CostLineReport.ImageCalculationExclude;
}
else
{
this._Costlinereportdata.Calculationstate = CalculationState.Included;
this.picCalculation.Image = CostLineReport.ImageCalculationInclude;
}
}
public void SetCalculateState(CalculationState st)
{
switch (st)
{
case CalculationState.Included:
this._Costlinereportdata.Calculationstate = CalculationState.Excluded;
break;
case CalculationState.Excluded:
this._Costlinereportdata.Calculationstate = CalculationState.Included;
break;
}
this.SwitchCalculateState(this, null);
}
#endregion
#region INTERFACE_IMEPLEMENTS
void IDisposable.Dispose()
{
this._Costlinereportdata = null;
this.picApprove.Image.Dispose();
this.picCalculation.Image.Dispose();
this.picCollapse.Image.Dispose();
this.picDetail.Image.Dispose();
this.picDisapprove.Image.Dispose();
this.picViewRecords.Image.Dispose();
this.rtDetail.Dispose();
this.rtMainData.Dispose();
this.lblDivider.Dispose();
this.lblDivider2.Dispose();
this.lblDivider3.Dispose();
this.lblDivider4.Dispose();
this.lblTextDivider.Dispose();
this.lblTitle.Dispose();
this.picApprove.Dispose();
this.picCalculation.Dispose();
this.picCollapse.Dispose();
this.picDetail.Dispose();
this.picDisapprove.Dispose();
this.picViewRecords.Dispose();
this.pnlColorIndicator.Dispose();
ttpApprove.Dispose();
ttpDisapprove.Dispose();
ttpCollapse.Dispose();
ttpDetail.Dispose();
ttpViewRecords.Dispose();
ttpCalculation.Dispose();
base.Dispose(true);
}
#endregion
}
}
This is the content (as far as winform controls go) for my user control
CostLineReport - System.Windows.Forms.UserControl
-lblDivider - System.Windows.Forms.Label
-lblDivider2 - System.Windows.Forms.Label
-lblDivider3 - System.Windows.Forms.Label
-lblDivider4 - System.Windows.Forms.Label
-lblTextDivider - System.Windows.Forms.Label
-lblTitle - System.Windows.Forms.Label
-lblTopDivider - System.Windows.Forms.Label
-picApprove - System.Windows.Forms.PictureBox
-picCalculation - System.Windows.Forms.PictureBox
-picCollapse - System.Windows.Forms.PictureBox
-picDetail - System.Windows.Forms.PictureBox
-picDisapprove - System.Windows.Forms.PictureBox
-picViewRecords - System.Windows.Forms.PictureBox
-pnlColorIndicator - System.Windows.Forms.Panel
-rtDetail - System.Windows.Forms.RichTextBox
-rtMaindata - System.Windows.Forms.RichTextBox
This is how I clear my flowlayoutpanel of all content:
while (pnlCenterRightControls.Controls.Count > 0)
{
pnlCenterRightControls.Controls[0].Dispose();
}
GC.Collect();
This is how I add my controls
I have added this block because this also adds a Delegate (event) method to one of the internal controls in the User control.
foreach (Classes.CustomControls.CostLineReportData c in SelectedCostlineReports)
{
Classes.CustomControls.CostLineReport r = c.ToControl();
r.CalculateControl.Click += delegate(object o, EventArgs e) { GetCostCalculation(); };
this.pnlCenterRightControls.Controls.Add(r);
}
EDIT SOLUTION
For those of you who are interested, I have updated my code to following:
Removing controls from the flowlayoutpanel
I also delete a subscriber to the event here
while (pnlCenterRightControls.Controls.Count > 0)
{
foreach (Control contr in pnlCenterRightControls.Controls)
{
Classes.CustomControls.CostLineReport clrp = contr as Classes.CustomControls.CostLineReport;
clrp.CalculateControl.Click -= GetCostCalculation;
clrp.Dispose();
}
}
Disposing my objects
(not disposing my static images though)
new public void Dispose()
{
this.Dispose(true);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
this.picApprove.Click -= Approve;
this.picDisapprove.Click -= Disapprove;
this.picDetail.Click -= ResizeControl;
this.picCollapse.Click -= CollapseControl;
this.picViewRecords.Click -= ShowRecords;
this.picCalculation.Click -= SwitchCalculateState;
this._Costlinereportdata = null;
this.rtDetail.Dispose();
this.rtMainData.Dispose();
this.lblDivider.Dispose();
this.lblDivider2.Dispose();
this.lblDivider3.Dispose();
this.lblDivider4.Dispose();
this.lblTextDivider.Dispose();
this.lblTitle.Dispose();
this.lblToplDivider.Dispose();
this.picApprove.Dispose();
this.picCalculation.Dispose();
this.picCollapse.Dispose();
this.picDetail.Dispose();
this.picDisapprove.Dispose();
this.picViewRecords.Dispose();
this.pnlColorIndicator.Dispose();
ttpApprove.Dispose();
ttpDisapprove.Dispose();
ttpCollapse.Dispose();
ttpDetail.Dispose();
ttpViewRecords.Dispose();
ttpCalculation.Dispose();
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
It is hard to tell without having the code available to run through a profiler but one thing I do notice is you are not -= your events. Any handlers for your events will still contain a reference to your objects and as such will make them ineligible for garbage collection.
EDIT:
There are two sets of events to worry about, those you have internally in your control (which you will need to manually remove in your dispose method) and those external ones which reference you control or it's children.
Any automatic way I have seen to do this is buggy or does not work great, in general I find it best to do it manually. See this answer for more info on auto removing events.
How to remove all event handlers from a control
From what it looks like you should dispose of all of these objects as well:
ImageApprove.Dispose;
ImageDisapprove.Dispose;
ImageDetail.Dispose;
ImageCollapse.Dispose;
ImageViewRecords.Dispose;
ImageCalculationInclude.Dispose;
ImageCalculationExclude.Dispose;
C# is garbage collected so it make no sense to have so many Dispose in your code.
Usually, you don't have to care much about anything that was added by the designer but only by your own code (one should expected generated code to be correct).
Also since UserControl already implement protected override void Dispose(bool disposing)you should really override that method instead of trying to re-implement the interface.
I don't understand how your code could even works... as your code probably call Dispose multiple times on the same object. I guess that predefined controls ignore the second call as otherwise you would have exception because an object is already disposed.
Any control added by the designer will be automatically disposed.
Calling Dispose on an inner object is absolute total nonsense. Each object should dispose its own objects. Always. Period.
picApprove.Image.Dispose(); // BAD CODE!!!
Obviously, when you manually add a control and manually connect an event handler, you generally have to manually remove both. Although your syntax is probably equivalent, I would recommend you to use the same short syntax when you add an event that when you remove it (except += instead of -=). That way, it is easier to ensure that they both matches. Use:
r.CalculateControl.Click += GetCostCalculation;
instead of:
r.CalculateControl.Click +=
delegate(object o, EventArgs e) { GetCostCalculation(); };
And why are you manually adding event handlers for controls that are added by the designer? I don't see any advantage of initializing part of the control in the designer and other part in the constructor. You will just increase the risk of regression bug if someone else maintain that code and think that the event was not properly connected.
new public void Dispose() is another complete non sense.
Finally, I see many other problem with your code... It does not follows many good design practices. I would recommend you to learn about SOLID design patterns.
A few examples of poor practices:
Public properties that return internal controls. It should generally not be necessary as the code that operate on those control should reside in this class. You might search information on TDA principle too.
Tight coupling between UI and business code.
Duplicate code (DRY principle) in Recolor function.
Nomenclature that does not respect .NET convention particularly for event handler function name.

Running a Windows application as a Command Line application

I am working on a migration tool that is primarily a Windows Forms application. What I want to do is provide the ability to run the application as a sort of command line utility where parameters can be passed in and the migration occurs completely void of the GUI. This seems straight forward enough and the entry point for my application looks like this:
[STAThread]
static void Main(string[] args)
{
if (args.Length == 0)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainWindow());
}
else
{
//Command Line Mode
Console.WriteLine("In Command Line Mode");
Console.ReadLine();
}
}
The problem I am running into is that when execution passes into the else block the text is not wrote back to the user at the command prompt which is problematic as I want to update the user at the command prompt as various executions complete. I could easily write a standalone console application but I was hoping to provide a single tool that allowed to different types of entries for a given scenario. Is what I am looking to do possible and, if so, how is it achieved?
Thanks!
Here is a topic that discusses this: http://social.msdn.microsoft.com/forums/en-US/Vsexpressvb/thread/875952fc-cd2c-4e74-9cf2-d38910bde613/
The key is calling AllocConsole function from kernel32.dll
The usual pattern for this would be to write the logic into a class library that you either call from a visual UI or from a command line application.
For example, if on the UI you accepted "Width", "Height" and "Depth" and then calculated volume you would put the calculation into the class library.
So you have either a Console app accepting three arguments, or a forms app with three inputs, and in both cases, they make the same call...
var volumeCalculations = new VolumeCalculations();
var volume = volumeCalculations.GetVolume(width, height, depth);
The console app is very thin and the forms app is very thin, because all they do is get the inputs to pass to the class library.
Here is completed runnable example. Compile with:
csc RunnableForm.cs RunnableForm.Designer.cs
RunnableForm.cs:
using System;
using System.Linq;
using System.Windows.Forms;
namespace Test
{
public partial class RunnableForm : Form
{
public RunnableForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("bang!");
}
[STAThread]
static void Main()
{
string[] args = Environment.GetCommandLineArgs();
// We'll always have one argument (the program's exe is args[0])
if (args.Length == 1)
{
// Run windows forms app
Application.Run(new RunnableForm());
}
else
{
Console.WriteLine("We'll run as a console app now");
Console.WriteLine("Arguments: {0}", String.Join(",", args.Skip(1)));
Console.Write("Enter a string: ");
string str = Console.ReadLine();
Console.WriteLine("You entered: {0}", str);
Console.WriteLine("Bye.");
}
}
}
}
RunnableForm.Designer.cs:
namespace Test
{
partial class RunnableForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(42, 42);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(153, 66);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// RunnableForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 261);
this.Controls.Add(this.button1);
this.Name = "RunnableForm";
this.Text = "RunnableForm";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button button1;
}
}

winforms Label flickering

I have a problem with the Label control that is terribly flickering.
Below is some code to reproduce the problem.
How to solve this?
UPDATE: The solution for the previous case (a Form contains a Label directly) was to make the form.DoubleBuffered = true. But this is not a generic solution. For example, what should I do in the case of label inside a SplitContainer? this is my real case.
UPDATED CODE:
DoubleBufferedLabel.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace FlickerLabelTest
{
public class DoubleBufferedLabel : Label
{
public DoubleBufferedLabel()
{
DoubleBuffered = true;
}
}
}
DoubleBufferedSplitContainer.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace FlickerLabelTest
{
public class DoubleBufferedSplitContainer : SplitContainer
{
public DoubleBufferedSplitContainer()
{
DoubleBuffered = true;
}
}
}
Form1.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace FlickerLabelTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
label1.Text += "0";
}
}
}
Form1.Designer.cs:
namespace FlickerLabelTest
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.timer1 = new System.Windows.Forms.Timer(this.components);
this.label1 = new FlickerLabelTest.DoubleBufferedLabel();
this.splitContainer1 = new DoubleBufferedSplitContainer();
this.splitContainer1.Panel2.SuspendLayout();
this.splitContainer1.SuspendLayout();
this.SuspendLayout();
//
// timer1
//
this.timer1.Enabled = true;
this.timer1.Interval = 1;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
//
// label1
//
this.label1.Dock = System.Windows.Forms.DockStyle.Fill;
this.label1.Location = new System.Drawing.Point(0, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(186, 262);
this.label1.TabIndex = 0;
this.label1.Text = "label1";
//
// splitContainer1
//
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer1.Location = new System.Drawing.Point(0, 0);
this.splitContainer1.Name = "splitContainer1";
//
// splitContainer1.Panel2
//
this.splitContainer1.Panel2.Controls.Add(this.label1);
this.splitContainer1.Size = new System.Drawing.Size(284, 262);
this.splitContainer1.SplitterDistance = 94;
this.splitContainer1.TabIndex = 1;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 262);
this.Controls.Add(this.splitContainer1);
this.DoubleBuffered = true;
this.Name = "Form1";
this.Text = "Form1";
this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
this.splitContainer1.Panel2.ResumeLayout(false);
this.splitContainer1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Timer timer1;
private DoubleBufferedLabel label1;
private DoubleBufferedSplitContainer splitContainer1;
}
}
Program.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace FlickerLabelTest
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
The problem is with the docking. If you change the Label.Dock from Fill to None, manually set the size of the label to fill the split panel and then anchor it on all sides, it won't flicker.
If you want to see the cause of the flicker, while Dock is still set to Fill, override OnResize in your DoubleBufferedLabel class, start the application, and while it is running set a breakpoint in OnResize. Add a watch for the Size and you'll see it flipping between its design time and runtime sizes.
I tried using a regular SplitContainer and Label in your example, set DoubleBuffer to False on the form, and it did not flicker if I left Dock set to None on the Label.
I think you're looking for this: http://msdn.microsoft.com/en-us/library/3t7htc9c.aspx
example:
class Form1 : Form
{
public Form1() {
this.DoubleBuffered = true;
}
}
Paste this into your form code to help the Dock layout calculations:
protected override void OnLoad(EventArgs e) {
label1.Size = this.ClientSize;
base.OnLoad(e);
}
Not really an answer, but why would you want to update your label each millisecond?
If you meant 1 second, you'd have to set your interval to 1000.
You can solve this by giving the form time to redraw itself and using a larger interval.
Update: turned out, setting DoubleBuffered to true solves the problem. Thanks for csharptest.net for pointing this out and DxCK for correcting me.
Activating Double Buffering on the form will fix the problem. But that is actually an expensive solution. If you just add the following to the form:
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
the flicker will be over too.
The default behavior of the Windows OS it to first let all windows paint their background, and later, let them do actual painting. This is from the old days, when painting letters actually took considerable amount of time. This flag tells it to fold the background paint and the regular paint (for the same window) immediately after each other.
Real Double buffering can be reserved for the cases where you actually do painting yourself (when you override OnPaint).
Stop setting timers to 1ms. No seriously, what you see here is that the Label tries to keep up with it's changes, but fails to do so because they're to frequent. So possible solutions are:
Choose a way to change the Label less often
Activate Double-Buffering on the Form
Why not run your label update function via an asynchronous delegate? Or use System.Threading namespace for a different flavor.
Also, as people before me mentioned, it would be useful if you set the DoubleBuffer property on your form to true (it's no silver bullet though).

Tooltip that's not linked to any particular control

I'm trying to make something like a spellchecker, that will list possible words under the current caret position. I thought I would do this by creating a tooltip, moving it according to the caret's location, and changing the text inside the tooltip.
I'm having problems.
I'm trying to show the tooltip with tip.Show(form, x, y);
However, this app is running from the systray. It has no GUI elements aside from that? What do I use as the form parameter? the notifyIcon1, Form1, etc. do not work.
I would start with an example that displayed a static tooltip that moved along with my mouse cursor or something. Can someone point me in the right direction?
Thanks
You may be able to do this but not using a tooltip class as that is quite limiting, there is a fantastic tooltip helper called VXPLib, using html formatting (which I suppose would give your listing of words an edge - say in different colours). The VXPLib is a COM object (written in C++) but accessible from the .NET language and there is a wrapper that can do it for you along with code samples. I have tried them and they actually work and make it look nice...See here for more information.
Hope this helps,
Best regards,
Tom.
I posted an answer in this thread that uses a transparent, maximized for to simulate drawing a tooltip anywhere on the screen, including the desktop. Maybe it will help: Creating a tooltip from a system-tray only app
Edit: Copied the code over from the linked post for ease of reading :-)
Here you go, use a transparent, maximized form that you BringToFront() before showing the ToolTip
Form1 Code:
using System;
using System.Windows.Forms;
namespace SO_ToolTip
{
public partial class Form1 : Form
{
Random _Random = new Random();
ToolTip _ToolTip = new ToolTip();
public Form1()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
BringToFront();
_ToolTip.Show("Blah blah... Blah blah... Blah blah...", this, _Random.Next(0, Width), _Random.Next(0, Height), 10000);
}
}
}
Form1 Designer Code: So you can see the forms properties:
namespace SO_ToolTip
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.timer1 = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout();
//
// timer1
//
this.timer1.Enabled = true;
this.timer1.Interval = 1000;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 264);
this.ControlBox = false;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "Form1";
this.Opacity = 0;
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Timer timer1;
}
}
Update: With ControlBox = false; and Opacity = 0; the form is not only visually transparent but is immune to user input. That is even when the Form1 above if the top most window clicking in it's area falls through to the next window/desktop. Just as if the form wasn't there. The BringToFront() before showing the tooltip is required because otherwise the tooltip could be drawn under other windows, which is not what you want.
If there's no GUI in your application, then in what application are you to providing a spell checker?
If you are integrating your application with another existing application (even non-.NET applications), then you need to obtain the handle (HWND) of the other application and convert it to a System.Windows.Forms.IWin32Window. Once you do this, you can use that handle as the form in the ToolTip.Show method.
Here is the code you need:
using System.Diagnostics;
//...
public class MyWindow : System.Windows.Forms.IWin32Window
{
private IntPtr _hwnd;
public IntPtr Handle
{
get
{
return _hwnd;
}
}
public MyWindow(IntPtr handle)
{
_hwnd = handle;
}
//...
public static MyWindow GetWindowFromName(string processName)
{
Process[] procs = Process.GetProcessesByName(processName);
if (procs.Length != 0)
{
return new MyWindow(procs[0].MainWindowHandle);
}
else
{
throw new ApplicationException(String.Format("{0} is not running", processName));
}
}
}
//...
tip.Show("this worked...", MyWindow.GetWindowFromName("Notepad"), 0, 0, 2000);
I have worked on creating a tooltip that is "not linked to any particular control", because I wanted to replace one of my AutoHotkey scripts which uses the ToolTip command.
I have my code stored at: https://bitbucket.org/tahir-hassan/dotnettooltip
All you do is, instantiate the control, set the text it displays, set the coordinates, and call Show method:
var tooltip = new ToolTipLib.ToolTip()
{
Text = "this is a nice toolTip",
LocationX = 100,
LocationY = 200
};
tooltip.Show();

Categories