Files
CommonLib/CommonLib/Library/Windows/Forms/FormControlsManipThreadSafe.cs
2025-03-13 18:15:01 -07:00

742 lines
35 KiB
C#

///======================================================================================
/// File: FormControlsManipThreadSafe.cs
/// Created: 06/06/12
///======================================================================================
/// <summary>
/// Provide a mechanism so that the main thread(main form) or its backgroundworker can
/// manipulate controls in a container
/// </summary>
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ----------- ----------- ----- ------------------------------------------
/// 06/06/12 D.Le
///======================================================================================
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 System.Threading;
using System.Text.RegularExpressions;
namespace CommonLib.Windows.Forms
{
///==========================================================================
/// FormControlsManipThreadSafe
///==========================================================================
/// <summary>
/// This class allows backgroundworker(child threads) to manipulate controls
/// in a container
/// </summary>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- --------------------------
/// 06/06/12 D.Le
///==========================================================================
public class FormControlsManipThreadSafe
{
public enum ControlVisibility { Display, Hidden };
///==========================================================================
/// FormControlsManipThreadSafe.AddControlToContainer
///==========================================================================
/// <summary>
/// Add a control to a container(form, panel, etc)
/// </summary>
/// <param name="control">any form control</param>
/// <param name="container">a container control (form, panel, groupbox,
/// tabcontrol, etc)</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public void AddControlToContainer(Control control, Control container)
{
// when a different thread trying to access the value this control on a form that was created by another thread
if (container.InvokeRequired)
{
container.Invoke(new Action(() => AddControlToContainer(control, container)));
}
// when the thread tries to access the value of the control on the form that belongs to the same thread
else
{
container.Controls.Add(control);
}
}
///==========================================================================
/// FormControlsManipThreadSafe.ModifyLabelToDisplayWithinContainerVisibleWidth
///==========================================================================
/// <summary>
/// If the text in a label is too long and it goes past the boundary of the
/// container, want to shorten it so that it ends at the boundary of the
/// container. 3 periods will be inserted in the middle of the text of the
/// label to indicate that text were cut off in the middle
/// </summary>
/// <param name="control">a label or linklabel control</param>
/// <param name="widthOfContainingPanel">the width of the container in which
/// the control is located</param>
/// <param name="d_rightMargin">the right margin of the control's text with
/// respect to the container in which it is located</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public void ModifyLabelToDisplayWithinContainerVisibleWidth(Control control, int widthOfContainingPanel, int d_rightMargin)
{
int minimumTextLength = 10, minimumWidthOfContainingPanel = 150;
int rightMargin = 2;
string connector = "...";
bool controlTextOverrunsVisibleLine = false;
// when a different thread trying to access the value this control on a form that was created by another thread
if (control.InvokeRequired)
{
control.Invoke(new Action(() => ModifyLabelToDisplayWithinContainerVisibleWidth(control, widthOfContainingPanel, d_rightMargin)));
}
else
{
// only perform this for LinkLabel and Label types
if (control.GetType() != typeof(LinkLabel) && control.GetType() != typeof(Label))
return;
if (control.Text.Length < minimumTextLength || widthOfContainingPanel < minimumWidthOfContainingPanel)
return;
if (d_rightMargin > rightMargin)
rightMargin = d_rightMargin;
string text = control.Text;
Size textsize = TextRenderer.MeasureText(text, control.Font);
int maxTextLength = widthOfContainingPanel - control.Left - rightMargin;
string left = text.Substring(0, (int)Math.Floor(text.Length / 2.0));
string right = text.Substring(left.Length, (int)Math.Ceiling(text.Length / 2.0));
while (textsize.Width > maxTextLength)
{
if (!controlTextOverrunsVisibleLine)
controlTextOverrunsVisibleLine = true;
if (left.Length >= right.Length)
left = left.Substring(0, left.Length - 1);
else
right = right.Substring(1, right.Length - 1);
string newText = left + connector + right;
textsize = TextRenderer.MeasureText(newText, control.Font);
}
if (controlTextOverrunsVisibleLine)
control.Text = left + connector + right;
}
}
///==========================================================================
/// FormControlsManipThreadSafe.RemoveControlFromContainer
///==========================================================================
/// <summary>
/// Remove a control from a container(form, panel, etc)
/// </summary>
/// <param name="control">any form control</param>
/// <param name="container">a container control (form, panel, groupbox,
/// tabcontrol, etc)</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public void RemoveControlFromContainer(Control control, Control container)
{
// when a different thread trying to access the value this control on a form that was created by another thread
if (container.InvokeRequired)
{
container.Invoke(new Action(() => RemoveControlFromContainer(control, container)));
}
// when the thread tries to access the value of the control on the form that belongs to the same thread
else
{
container.Controls.Remove(control);
}
}
///==========================================================================
/// FormControlsManipThreadSafe.BaseClassObjectIsOfASpecificType
///==========================================================================
/// <summary>
/// Given any class object, usually of type base class, determine if the object
/// is of a specific type
/// </summary>
/// <param name="ojb">an object</param>
/// <param name="objectSpecificType">a type</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public bool BaseClassObjectIsOfASpecificType(object obj, Type objectSpecificType)
{
if (obj.GetType().IsAssignableFrom(objectSpecificType))
return true;
else
return false;
}
///==========================================================================
/// FormControlsManipThreadSafe.RemoveControlFromContainer
///==========================================================================
/// <summary>
/// Resize a container (form, panel, groupbox, etc)
/// </summary>
/// <param name="container">a container control (form, panel, groupbox,
/// tabcontrol, etc)</param>
/// <param name="resizeWidth">width to resize to</param>
/// <param name="resizeHeight">height to resize to</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public void ResizeContainer(Control container, int resizeWidth, int resizeHeight)
{
// when a different thread trying to access the value this control on a form that was created by another thread
if (container.InvokeRequired)
{
container.Invoke(new Action(() => ResizeContainer(container, resizeWidth, resizeHeight)));
}
// when the thread tries to access the value of the control on the form that belongs to the same thread
else
{
container.Width = resizeWidth;
container.Height = resizeHeight;
}
}
///==========================================================================
/// FormControlsManipThreadSafe.PositionControl
///==========================================================================
/// <summary>
/// Position a control within a container (form, panel, groupbox, etc)
/// </summary>
/// <param name="control">any form control</param>
/// <param name="pos">a Point object with X and Y position defined</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public void PositionControl(Control control, Point pos)
{
// when a different thread trying to access the value this control on a form that was created by another thread
if (control.InvokeRequired)
{
control.Invoke(new Action(() => PositionControl(control, pos)));
}
// when the thread tries to access the value of the control on the form that belongs to the same thread
else
{
control.Location = new System.Drawing.Point(pos.X, pos.Y);
}
}
///==========================================================================
/// FormControlsManipThreadSafe.SetControlVisibility
///==========================================================================
/// <summary>
/// Hide or Show a control on in the container
/// </summary>
/// <param name="control">any form control</param>
/// <param name="visibility">Enum type</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public void SetControlVisibility(Control control, ControlVisibility visibility)
{
// when a different thread trying to access the value this control on a form that was created by another thread
if (control.InvokeRequired)
{
control.Invoke(new Action(() => SetControlVisibility(control, visibility)));
}
// when the thread tries to access the value of the control on the form that belongs to the same thread
else
{
if (visibility == ControlVisibility.Display)
control.Visible = true;
else
control.Visible = false;
}
}
///==========================================================================
/// FormControlsManipThreadSafe.ModifyControlText
///==========================================================================
/// <summary>
/// Modify the text of a control
/// </summary>
/// <param name="control">any form control</param>
/// <param name="text">a string</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public void ModifyControlText(Control control, string text)
{
// when a different thread trying to access the value this control on a form that was created by another thread
if (control.InvokeRequired)
{
control.Invoke(new Action(() => ModifyControlText(control, text)));
}
// when the thread tries to access the value of the control on the form that belongs to the same thread
else
{
if (BaseClassObjectIsOfASpecificType(control, typeof(TextBox))
||
BaseClassObjectIsOfASpecificType(control, typeof(Button)))
control.Text = text;
else if (BaseClassObjectIsOfASpecificType(control, typeof(RichTextBox)))
{
RichTextBox rTextBox = control as RichTextBox;
rTextBox.AppendText(text);
}
}
}
///==========================================================================
/// FormControlsManipThreadSafe.RichTextBoxEraseLine
///==========================================================================
/// <summary>
/// Erase a paritcular line in Rich Text Box with the option to delete the
/// line completely.
/// Erase is just erasing all the characters on a line, but the line still
/// exists. If DeleteLine option is true, the whole line will be removed
/// </summary>
/// <param name="control">rich text box control</param>
/// <param name="lineIndex">line index, starting at index 0</param>
/// /// <param name="deleteLine">deleteLine</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public void RichTextBoxEraseLine(RichTextBox control, int lineIndex, bool deleteLine)
{
// when a different thread trying to access the value this control on a form that was created by another thread
if (control.InvokeRequired)
{
control.Invoke(new Action(() => RichTextBoxEraseLine(control, lineIndex, deleteLine)));
}
// when the thread tries to access the value of the control on the form that belongs to the same thread
else
{
int LineCharStartIndex;
int LineCharEndIndex;
int numCharsToBeRemove;
if (lineIndex >= 0 && lineIndex < control.Lines.Count())
{
LineCharStartIndex = control.GetFirstCharIndexFromLine(lineIndex);
if (control.Lines.Count() > (lineIndex + 1))
LineCharEndIndex = control.GetFirstCharIndexFromLine(lineIndex + 1) - 1;
else
LineCharEndIndex = control.TextLength;
numCharsToBeRemove = LineCharEndIndex - LineCharStartIndex;
if (numCharsToBeRemove > 0)
{
control.Select(LineCharStartIndex, numCharsToBeRemove);
control.SelectedText = "";
if (deleteLine)
{
if (lineIndex > 0 && control.Lines.Count() > 1)
{
// remove the line, this just means that we just shifted all the lines
// below this line up 1 line. The last line is empty
LineCharStartIndex = control.GetFirstCharIndexFromLine(lineIndex) - 1;
control.Select(LineCharStartIndex, 1);
control.SelectedText = "";
}
else if (lineIndex == 0 && control.Lines.Count() > 1)
{
// remove the line, this just means that we just shfited all the lines
// below this line up 1 line. The last line is empty
LineCharStartIndex = control.GetFirstCharIndexFromLine(lineIndex);
control.Select(LineCharStartIndex, 1);
control.SelectedText = "";
}
}
}
else if (lineIndex <= control.Lines.Count() - 1) // remove blank line
{
if (deleteLine)
{
if (lineIndex > 0)
{
control.Select(control.GetFirstCharIndexFromLine(lineIndex) - 1, 1);
}
else
{
control.Select(control.GetFirstCharIndexFromLine(lineIndex), 1);
}
control.SelectedText = "";
}
}
}
}
}
///==========================================================================
/// FormControlsManipThreadSafe.ModifyRichTextBoxText
///==========================================================================
/// <summary>
/// Modify the text of a rich text box in only 1 color
/// </summary>
/// <param name="control">rich text box control</param>
/// <param name="text">a string</param>
/// <param name="fontProp">font properties</param>
/// <param name="textColor">color property</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public void ModifyRichTextBoxText(RichTextBox control, string text, Font fontProp, Color textColor)
{
// when a different thread trying to access the value this control on a form that was created by another thread
if (control.InvokeRequired)
{
control.Invoke(new Action(() => ModifyRichTextBoxText(control, text, fontProp, textColor)));
}
// when the thread tries to access the value of the control on the form that belongs to the same thread
else
{
control.SelectionFont = fontProp;
control.SelectionColor = textColor;
control.AppendText(text);
}
}
///==========================================================================
/// FormControlsManipThreadSafe.ModifyRichTextBoxTextAtLine
///==========================================================================
/// <summary>
/// Modify the text of a rich text box in only 1 color at a particular line
/// starting a particular character index on that line
/// </summary>
/// <param name="control">rich text box control</param>
/// <param name="lineIndex">line index of the text box starting at 0</param>
/// <param name="charsCount">number of characters that have already been processed</param>
/// <param name="text">a string</param>
/// <param name="fontProp">font properties</param>
/// <param name="textColor">color property</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public bool ModifyRichTextBoxTextAtLine(RichTextBox control, int lineIndex, int charsCount, string word, Font fontProp, Color textColor)
{
bool success = true;
// when a different thread trying to access the value this control on a form that was created by another thread
if (control.InvokeRequired)
{
return (bool)control.Invoke(new Func<bool>(
delegate
{
return ModifyRichTextBoxTextAtLine(control, lineIndex, charsCount, word, fontProp, textColor);
}
));
}
// when the thread tries to access the value of the control on the form that belongs to the same thread
else
{
if (lineIndex < control.Lines.Count())
{
// determine the starting index of the new word
int startCharIndex = control.GetFirstCharIndexFromLine(lineIndex) + charsCount;
control.Select(startCharIndex, word.Length);
control.SelectionFont = fontProp;
control.SelectionColor = textColor;
control.SelectedText = control.SelectedText;
}
else
success = false;
return success;
}
}
///==========================================================================
/// FormControlsManipThreadSafe.getRichTextBoxLastLineIndex
///==========================================================================
/// <summary>
/// Modify text in rich text box in various colors and font at a particular
/// line
/// </summary>
/// <param name="control">rich text box control</param>
/// <param name="lineIndex">line index of the text box starting at 0</param>
/// <param name="textPropList">contains font properties for particular words</param>
/// <param name="defaultFont">the default font for words that are not defined in textPropList</param>
/// <param name="defaultFontColor">the default font color for words that are not defined in textPropList</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public int getRichTextBoxLastLineIndex(RichTextBox control)
{
// when a different thread trying to access the value this control on a form that was created by another thread
if (control.InvokeRequired)
{
return (int)control.Invoke(new Func<int>(
delegate
{
return getRichTextBoxLastLineIndex(control);
}
));
}
// when the thread tries to access the value of the control on the form that belongs to the same thread
else
{
return (control.Lines.Count() - 1);
}
}
///==========================================================================
/// FormControlsManipThreadSafe.ModifyRichTextBoxPerWordsAtLine
///==========================================================================
/// <summary>
/// Modify text in rich text box in various colors and font at a particular
/// line
/// </summary>
/// <param name="control">rich text box control</param>
/// <param name="lineIndex">line index of the text box starting at 0</param>
/// <param name="textPropList">contains font properties for particular words</param>
/// <param name="defaultFont">the default font for words that are not defined in textPropList</param>
/// <param name="defaultFontColor">the default font color for words that are not defined in textPropList</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public bool ModifyRichTextBoxPerWordsAtLine(RichTextBox control, int lineIndex, string text, List<TextFormat.TextFontAndColor> textPropList, Font defaultFont, Color defaultFontColor)
{
int wordIndex = 0;
int wordCount = 0;
int textPropListIndex = -1;
Regex ItemRegex = new Regex(@"\s*[^ ]+", RegexOptions.Compiled);
bool success = true;
// when a different thread trying to access the value this control on a form that was created by another thread
if (control.InvokeRequired)
{
return (bool)control.Invoke(new Func<bool>(
delegate
{
return ModifyRichTextBoxPerWordsAtLine(control, lineIndex, text, textPropList, defaultFont, defaultFontColor);
}
));
}
else
{
if (lineIndex < control.Lines.Count())
{
int startCharIndex = control.GetFirstCharIndexFromLine(lineIndex);
int endCharIndex = lineIndex < control.Lines.Count() - 1 ?
control.GetFirstCharIndexFromLine(lineIndex + 1) - 1 :
control.Text.Length;
// select the whole line
control.Select(startCharIndex, endCharIndex - startCharIndex);
control.SelectionFont = defaultFont;
control.SelectionColor = defaultFontColor;
// replace line with new text
control.SelectedText = text;
// number of characters in all the words that have been processed
int charsProcessed = 0;
foreach (Match ItemMatch in ItemRegex.Matches(text))
{
if (wordCount == 0)
textPropListIndex = getTextPropListIndexForWordIndex(textPropList, wordIndex);
if ((textPropListIndex >= 0 && wordIndex == textPropList[textPropListIndex].wordIndex) || wordCount > 0)
{
if (wordCount == 0)
wordCount = textPropList[textPropListIndex].wordCount;
FormControlsManipThreadSafe.ModifyRichTextBoxTextAtLine(control, lineIndex, charsProcessed, ItemMatch.ToString(), textPropList[textPropListIndex].textFont, textPropList[textPropListIndex].textColor);
wordCount--;
if (wordCount == 0)
{
textPropList.RemoveAt(textPropListIndex);
}
}
else
{
FormControlsManipThreadSafe.ModifyRichTextBoxTextAtLine(control, lineIndex, charsProcessed, ItemMatch.ToString(), defaultFont, defaultFontColor);
}
wordIndex++;
charsProcessed += ItemMatch.ToString().Length;
}
if (textPropList != null)
textPropList.Clear();
Match regExMatch;
regExMatch = Regex.Match(text, @"[^ ]?( +)$");
// if there are spaces at the end of text, want to display it
if (regExMatch.Success)
{
FormControlsManipThreadSafe.ModifyRichTextBoxTextAtLine(control, lineIndex, charsProcessed, regExMatch.Groups[1].Value, defaultFont, defaultFontColor);
}
}
else
success = false;
return success;
}
}
///==========================================================================
/// FormControlsManipThreadSafe.ModifyRichTextBoxPerWords
///==========================================================================
/// <summary>
/// Modify text in rich text box in various colors and font
/// </summary>
/// <param name="control">rich text box control</param>
/// <param name="textPropList">contains font properties for particular words</param>
/// <param name="defaultFont">the default font for words that are not defined in textPropList</param>
/// <param name="defaultFontColor">the default font color for words that are not defined in textPropList</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public void ModifyRichTextBoxPerWords(RichTextBox control, string text, List<TextFormat.TextFontAndColor> textPropList, Font defaultFont, Color defaultFontColor)
{
int wordIndex = 0;
int wordCount = 0;
int textPropListIndex = -1;
Regex ItemRegex = new Regex(@"\s*[^ ]+", RegexOptions.Compiled);
foreach (Match ItemMatch in ItemRegex.Matches(text))
{
if (wordCount == 0)
textPropListIndex = getTextPropListIndexForWordIndex(textPropList, wordIndex);
if ((textPropListIndex >= 0 && wordIndex == textPropList[textPropListIndex].wordIndex) || wordCount > 0)
{
if (wordCount == 0)
wordCount = textPropList[textPropListIndex].wordCount;
FormControlsManipThreadSafe.ModifyRichTextBoxText(control, ItemMatch.ToString(), textPropList[textPropListIndex].textFont, textPropList[textPropListIndex].textColor);
wordCount--;
if (wordCount == 0)
{
textPropList.RemoveAt(textPropListIndex);
}
}
else
{
FormControlsManipThreadSafe.ModifyRichTextBoxText(control, ItemMatch.ToString(), defaultFont, defaultFontColor);
}
wordIndex++;
}
if ( textPropList != null )
textPropList.Clear();
Match regExMatch;
regExMatch = Regex.Match(text, @"[^ ]?( +)$");
// if there are spaces at the end of text, want to display it
if (regExMatch.Success)
{
FormControlsManipThreadSafe.ModifyControlText(control, regExMatch.Groups[1].Value);
}
}
///==========================================================================
/// FormControlsManipThreadSafe.getTextPropListIndexForWordIndex
///==========================================================================
/// <summary>
/// textPropList contains a list of font properties of various words in a string
/// want to find the font property of a word or words starting at a particular word index
/// in the string
/// </summary>
/// <param name="textPropList">contains font properties for particular words</param>
/// <param name="wordIndex">index of the word in a string</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static private int getTextPropListIndexForWordIndex(List<TextFormat.TextFontAndColor> textPropList, int wordIndex)
{
int index = -1;
if (textPropList != null)
{
for (int i = 0; i < textPropList.Count; i++)
{
if (textPropList[i].wordIndex == wordIndex)
index = i;
}
}
return index;
}
///==========================================================================
/// FormControlsManipThreadSafe.ModifyControlColors
///==========================================================================
/// <summary>
/// Modify back color and fore color of a control
/// </summary>
/// <param name="control">any form control</param>
/// <param name="text">a string</param>
///==========================================================================
/// Date Programmer Proj.ID SAR REVISION HISTORY:
/// --/--/-- ------------ -------- ---- -----------------------------
/// 06/06/12 D.Le
///==========================================================================
static public void ModifyControlColors(Control control, Color backColor, Color foreColor)
{
// when a different thread trying to access the value this control on a form that was created by another thread
if (control.InvokeRequired)
{
control.Invoke(new Action(() => ModifyControlColors(control, backColor, foreColor)));
}
// when the thread tries to access the value of the control on the form that belongs to the same thread
else
{
if (backColor != null)
control.BackColor = backColor;
if (foreColor != null)
control.ForeColor = foreColor;
}
}
}
}