I have an app that is connected to a remote server and polling data when needed. It has a TreeView where the Nodes represent the objects that are available and the color of the text indicate whether the data has been loaded or not; gray-italicized indicates not loaded, black, regular text is loaded.
Currently I have set the TreeView to be OwnderDrawText and have the TreeView.DrawNode function simply draw the text as so:
private void TreeViewDrawNode(object sender, DrawTreeNodeEventArgs e)
{
if (!e.Node.IsVisible)
{
return;
}
bool bLoaded = false;
if (e.Bounds.Location.X >= 0 && e.Bounds.Location.Y >= 0)
{
if(e.Node.Tag != null)
{
//...
// code determining whether data has been loaded is done here
// setting bLoaded true or false
//...
}
else
{
e.DrawDefault = true;
return;
}
Font useFont = null;
Brush useBrush = null;
if (bLoaded)
{
useFont = e.Node.TreeView.Font;
useBrush = SystemBrushes.WindowText;
}
else
{
useFont = m_grayItallicFont;
useBrush = SystemBrushes.GrayText;
}
e.Graphics.DrawString(e.Node.Text, useFont, useBrush, e.Bounds.Location);
}
}
I figured that would be enough, however, this has been causing some issues;
When a node is selected, focused or not, it doesn't envelop all of the text, example (I hope imgur is ok).
When the node is focused, the dotted outline doesn't show either. If you compare it with this example. The nodes with the "log" in the text are using the e.DefaultDraw = true
I tried following the example given in this question. It looked something like this:
private void TreeViewDrawNode(object sender, DrawTreeNodeEventArgs e)
{
if (!e.Node.IsVisible)
{
return;
}
bool bLoaded = false;
if (e.Bounds.Location.X >= 0 && e.Bounds.Location.Y >= 0)
{
if(e.Node.Tag != null)
{
//...
// code determining whether data has been loaded is done here
// setting bLoaded true or false
//...
}
else
{
e.DrawDefault = true;
return;
}
//Select the font and brush depending on whether the property has been loaded
Font useFont = null;
Brush useBrush = null;
if (bLoaded)
{
useFont = e.Node.TreeView.Font;
useBrush = SystemBrushes.WindowText;
}
else
{
//member variable defined elsewhere
useFont = m_grayItallicFont;
useBrush = SystemBrushes.GrayText;
}
//Begin drawing of the text
//Get the rectangle that will be used to draw
Rectangle itemRect = e.Bounds;
//Move the rectangle over by 1 so it isn't on top of the check box
itemRect.X += 1;
//Figure out the text position
Point textStartPos = new Point(itemRect.Left, itemRect.Top);
Point textPos = new Point(textStartPos.X, textStartPos.Y);
//generate the text rectangle
Rectangle textRect = new Rectangle(textPos.X, textPos.Y, itemRect.Right - textPos.X, itemRect.Bottom - textPos.Y);
int textHeight = (int)e.Graphics.MeasureString(e.Node.Text, useFont).Height;
int textWidth = (int)e.Graphics.MeasureString(e.Node.Text, useFont).Width;
textRect.Height = textHeight;
//Draw the highlighted box
if ((e.State & TreeNodeStates.Selected) != 0)
{
//e.Graphics.FillRectangle(SystemBrushes.Highlight, textRect);
//use pink to see the difference
e.Graphics.FillRectangle(Brushes.Pink, textRect);
}
//widen the rectangle by 3 pixels, otherwise all of the text won't fit
textRect.Width = textWidth + 3;
//actually draw the text
e.Graphics.DrawString(e.Node.Text, useFont, useBrush, e.Bounds.Location);
//Draw the box around the focused node
if ((e.State & TreeNodeStates.Focused) != 0)
{
textRect.Width = textWidth;
Pen focusPen = new Pen(Color.Black);
focusPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
e.Graphics.DrawRectangle(focusPen, textRect);
}
}
}
However, the results were this. (Note, used pink to differentiate the colors). As you can see, the highlighted background doesn't extend all the way to where the focused dotted line is at. And there's also another box that is drawn as well.
I'm slightly stumped on how to fix this. All I want is to have gray italicized text when something is loaded. The first and simplest approach doesn't quite work and the second method feels like I'm doing way too much.
After all that, does anyone have any suggestions on how to do this properly, because there's got to be a simpler way.
Thank you in advance.
You'll need to use TextRenderer.DrawText(). That's what TreeView uses, it renders text slightly different from Graphics.DrawString().
Related
I am using SourceGrid (https://www.nuget.org/packages/SourceGrid) in my WinForms app and I want to custom draw a cell. I have found a way to do it, I just feel there must be a better, less hacky way. Here is what I do:
When creating a new cell:
Cell c = new Cell(Value);
var v = new SourceGrid.Cells.Views.Cell();
v.ElementText = new MyCustomCellElement(); // this will draw for me
c.View = v;
Later when filling the cells with data:
((Grid[row][col].View as SourceGrid.Cells.Views.Cell).ElementText as MyCustomCellElement).MyCustomData = MyCustomObj;
And the code that does the custom draw:
class MyCustomCellElement : DevAge.Drawing.VisualElements.TextGDI
{
public MyObject MyCustomData { get; set; }
protected override void OnDraw(GraphicsCache graphics, RectangleF area)
{
if (Value == null || Value.Length == 0)
return;
SolidBrush brush;
if (Enabled)
brush = graphics.BrushsCache.GetBrush(ForeColor);
else
brush = graphics.BrushsCache.GetBrush(Color.FromKnownColor(KnownColor.GrayText));
// do your draw here, this is the default:
graphics.Graphics.DrawString(Value, Font, brush, area, StringFormat);
}
}
The double cast when putting data in the cell doesn't smell right - it is a hack. It works but I am sure there is a 'proper' way to do it.
I want to include the colored panel below into my form:
For this I have created custom panel which will change Border color based on Radio Button selection. My panel code is
InfoPanel.cs
class InfoPanel : Panel
{
private Color colorBorder = Color.Transparent;
public InfoPanel()
: base()
{
this.SetStyle(ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.DrawRectangle(
new Pen(
new SolidBrush(colorBorder), 2),
e.ClipRectangle);
e.Graphics.DrawLine(new Pen(new SolidBrush(colorBorder), 0), 50, 0, 50, 50); //drawing a line to split the child & parent info panel
}
public Color BorderColor
{
get
{
return colorBorder;
}
set
{
colorBorder = value;
}
}
}
In my form,
1. created one parent Info Panel
2. created one child panel with Picture box
3. One label in parent info panel to show the information
Now for the parent panel I am changing the colors [back, border] & text based on user selection & for child panel I am not changing border color but updating back color based on user selection.
Below is the code for changing the panel colors, image, text update:
private void rbIPAddress_CheckedChanged(object sender, EventArgs e)
{
if (rbIPAddress.Checked)
{
ParentInfoPanel.BackColor = System.Drawing.ColorTranslator.FromHtml("#FFFFEE");
ParentInfoPanel.BorderColor = System.Drawing.ColorTranslator.FromHtml("#DADA85");
ChildInfoPanel.BackColor = System.Drawing.ColorTranslator.FromHtml("#F6F6D8");
InfoPanelPictureBox.Image = Template.InfoPanelInfoImage;
Infolabel.Text = "IP Address is already configured. You can switch to Forward Lookup Zone by choosing other configuration. *IP Address \ncan be either LB IP Address.";
txtBoxIPAddress.Enabled = true;
textBoxPort.Enabled = true;
}
else
{
Infolabel.Text = "";
txtBoxIPAddress.Text = "";
txtBoxIPAddress.Enabled = false;
textBoxPort.Enabled = false;
}
}
private void rbForwardLookupZone_CheckedChanged(object sender, EventArgs e)
{
if (rbForwardLookupZone.Checked)
{
ParentInfoPanel.BackColor = System.Drawing.ColorTranslator.FromHtml("#FFFFEE");
ParentInfoPanel.BorderColor = System.Drawing.ColorTranslator.FromHtml("#DADA85");
ChildInfoPanel.BackColor = System.Drawing.ColorTranslator.FromHtml("#F6F6D8");
InfoPanelPictureBox.Image = Template.InfoPanelInfoImage;
Infolabel.Text = "Forward Lookup Zone is already configured. You can switch to IP Address by choosing other configuration and \nchanging port number will affect Firewall rules.";
textBoxControlPlane.Enabled = true;
if (string.IsNullOrEmpty(textBoxControlPlane.Text))
{
textBoxControlPlane.Text = Constants.DefaultControlPlaneDomain;
}
}
else
{
Infolabel.Text = "";
textBoxControlPlane.Text = "";
textBoxControlPlane.Enabled = false;
}
}
Note: used next line character to display label text in multiple line
Output: Everything is ok but in the end of label text I am getting another rectangle box. I'm wondering why is showing like this? Am I doing wrong? Please help me on this.
The issue is that you're using e.ClipRectangle. It informs you which portion of the control needs to be redrawn. This is sometimes only a small part of the control rather than the whole thing (in your case the area of the extra rectangle). Always draw the control's full rectangle instead.
Also, you must dispose of both the Pen and SolidBrush. Failing to do so causes memory leaks. Utilize the using statement.
using(SolidBrush brush = new SolidBrush(colorBorder))
using(Pen pen = new Pen(brush, 2))
{
e.Graphics.DrawRectangle(pen, new Rectangle(0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1));
e.Graphics.DrawLine(pen, 50, 0, 50, 50);
}
I am displaying a transparent icon (32x32) of a red triangle in the upper right of a button control that signifies an error exists. Additionally, when the user hovers over the icon, a tool tip is displayed.
I have been able to display the icon and the associated tool tip. The problem is a transparent 32x32 icon with the red triangle being only 12x12. The tool tip should only trigger when hovering over the red triangle and not the transparent space.
Attempts have been made to display the triangle as a button as well as a picture box, however the tool tip still triggers in the transparent space. Additionally, the error provider was first used as a goal of what I am trying to accomplish.
UI items:
Button control: "btnAttachments"
Error Provider control: "errManager"
public class StackTest
{
private static Dictionary<string, Control> _errorMessages = new Dictionary<string, Control>();
public StackTest()
{
InitializeComponent();
InitErrors();
}
private void InitErrors()
{
_errorMessages.Clear();
AddErrorControl(btnAttachments, "Missing file attachment(s).");
//errManager.SetError(btnAttachments, "Missing file attachment(s)."); errManager.SetIconPadding(btnAttachments, -32);
}
private void AddErrorControl(Control control, string message = null, Enum selectedImage = null, EventHandler handler = null)
{
string name = "errFor" + control.Name;
if (_errorMessages.ContainsKey(name)) { return; }
Button errorIcon = CreateErrorControl(name, control);
errorIcon.BackgroundImage = Theme.GetImage(selectedImage ?? eImages_OtherIcons.Error_TopRight_Small);
//PictureBox errorIcon = CreateErrorControl2(name);
//errorIcon.Image = Theme.GetImage(selectedImage ?? eImages_OtherIcons.Error_TopRight_Small);
//errorIcon.Image = Bitmap.FromHicon((Theme.GetIcon(selectedImage ?? eImages_OtherIcons.Error_TopRight_Small)).Handle);
if (null != handler) { errorIcon.Click += handler; }
new ToolTip().SetToolTip(errorIcon, message);
errorIcon.Tag = message;
control.Controls.Add(errorIcon);
control.Controls[name].Location = new Point(control.Width - errorIcon.Width +20 , 0 );
_errorMessages.Add(name, errorIcon);
}
private Button CreateErrorControl(string name, Control control)
{
var errorIcon = new Button();
errorIcon.Name = name;
errorIcon.Size = new Size(32, 32);
//errorIcon.Location = new Point(control.Width - errorIcon.Width, 0);
errorIcon.Cursor = Cursors.Hand;
errorIcon.FlatStyle = FlatStyle.Flat;
errorIcon.BackColor = Color.Fuchsia;
errorIcon.FlatAppearance.MouseDownBackColor = Color.Transparent;
errorIcon.FlatAppearance.MouseOverBackColor = Color.Transparent;
errorIcon.FlatAppearance.BorderSize = 0;
errorIcon.Visible = false;
return errorIcon;
}
private PictureBox CreateErrorControl2(string name)
{
var errorIcon = new PictureBox();
errorIcon.Name = name;
errorIcon.Size = new Size(32, 32);
errorIcon.Cursor = Cursors.Hand;
errorIcon.BackColor = Color.Transparent;
errorIcon.Visible = false;
return errorIcon;
}
}
The built in Error Provider control achieves the desire results that I would like to replicate. Doing so will allow for a more robust application with more custom functionality then what the error provider offers.
Based on the GetPixel suggestion from #TaW, I did further R&D and now have something functional. The Tag of the picture box contains the tool tip message to be displayed. With the picture box being the "sender" of the mouse move, it was easy to extract the image back to a bitmap.
Thanks to all for the feedback.
First, I switched the testing to use CreateErrorControl2 with the PictureBox and added in a MouseMove.
private PictureBox CreateErrorControl2(string name) //, Control control)
{
var errorIcon = new PictureBox();
errorIcon.Name = name;
errorIcon.Size = new Size(32, 32);
errorIcon.Cursor = Cursors.Default;
errorIcon.BackColor = Color.Transparent;
errorIcon.Visible = false;
errorIcon.MouseMove += new MouseEventHandler(DisplayToolTip);
return errorIcon;
}
The following code was also added in support of the DisplayToolTip method.
private bool _toolTipShown = false;
private bool IsTransparent(PictureBox pb, MouseEventArgs e)
{
Color pixel = ((Bitmap)pb.Image).GetPixel(e.X, e.Y);
return (0 == pixel.A && 0 == pixel.R && 0 == pixel.G && 0 == pixel.B);
}
private void DisplayToolTip(object sender, MouseEventArgs e)
{
Control control = (Control)sender;
IsTransparent((PictureBox)control, e);
if (IsTransparent((PictureBox)control, e))
{
_toolTip.Hide(control);
_toolTipShown = false;
}
else
{
if (!_toolTipShown)
{
_toolTip.Show(control.Tag.ToString(), control);
_toolTipShown = true;
}
}
}
My form has two objects: a background image that takes up the entire screen, and a dragable dialog control that only takes up a portion of the screen. I am using Mouse events for dragging and dropping the dialog control within the background image.
My problem is that while dragging, there is a copy of the control that is visible at it's previous location.
What is causing this shadow copy to appear, and how can I prevent it from happening?
My code looks something like this:
private void dialog_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
_dialogDragStart = e.Location;
dialog.MouseMove += new MouseEventHandler(dialog_MouseMove);
}
}
private void dialog_MouseUp(object sender, MouseEventArgs e)
{
dialog.MouseMove -= new MouseEventHandler(dialog_MouseMove);
_isDragging.Value = false;
}
private void dialog_MouseMove(object sender, MouseEventArgs e)
{
var difference = new Point(e.Location.X - _dialogDragStart.X, e.Location.Y - _dialogDragStart.Y);
if (!_isDragging.Value)
{
if (Math.Abs(difference.X) > SystemInformation.DragSize.Width || Math.Abs(difference.Y) > SystemInformation.DragSize.Height)
{
_isDragging.Value = true;
}
}
else if (_isDragging.Value && !difference.IsEmpty)
{
DialogOffset = GetValidDialogOffset(DialogOffset.X + difference.X, DialogOffset.Y + difference.Y);
this.Invalidate();
this.Update();
}
}
It should be noted that this entire UserControl contains a custom OnPaint event. The Paint event recalculates the Size and Location of the Dialog before painting it in a custom format. To get this working, I have simply adjusted the Location calculation to include the DialogOffset.
const int padding = 1;
const int shadowDepth = 3;
const int roudeCornerRadius = 5;
private void Paint(Graphics g, Rectangle clipRect)
{
try
{
dialog.Size = GetSize();
dialog.Location = new Point(
((backgroundImage.Width - dialog.Width) / 2) + DialogOffset.X,
((backgroundImage.Height - dialog.Height) / 2) + DialogOffset.Y);
if (Image != null && !backgroundImage.ClientRectangle.IsEmpty)
{
Rectangle imageBounds = new Rectangle(Point.Empty, Image.Size);
using (BufferedGraphics buffer = BufferedGraphicsManager.Current.Allocate(g, backgroundImage.ClientRectangle))
{
using (Bitmap img = ((Bitmap)backgroundImage).Clone(imageBounds, PixelFormat.Format16bppArgb1555) as Bitmap)
{
buffer.Graphics.Clear(Color.White);
buffer.Graphics.DrawImage(img,backgroundImage.ClientRectangle, imageBounds, GraphicsUnit.Pixel);
buffer.Render(g);
}
}
}
//Create Rectangles
Rectangle outerRect = dialog.ClientRectangle;
Rectangle innerRect = new Rectangle(padding, padding, outerRect.Width - (padding + shadowDepth), outerRect.Height - (padding + shadowDepth));
//create Paths
using (GraphicsPath innerPath = new GraphicsPath())
{
GraphicsHelper.GetRoundedRect(innerPath, innerRect, roudeCornerRadius, 1, 1);
//Assign outer rounded rectangle to the dialog's region property
Region region = new Region(innerPath);
if (dialog.Region != null)
dialog.Region.Dispose();
dialog.Region = region;
}
}
catch (Exception ex)
{
// Log error
}
}
I've seen suggestions to set the DoubleBufferred property to true, however its explicitly set to false in the existing code, and setting it to true causes no update at all while dragging.
I've tried taking a screenshot of the problem, however all screenshots only show one copy of the dialog control being dragged, even though I can clearly see a 2nd copy of the control on my screen while dragging.
What can be causing this, and how can I fix it?
I set the DrawMode to OwnerDrawText and tacked on to the DrawNode event, added my code to draw the text the way I want and all works well save for some odd black selection highlighting when a node is selected.
No problem, I added logic to check for if the node's state was highlighted and drew my own highlighting except the black highlighting gets added when a node is clicked, not just selected... The highlight gets drawn over by my rectangle once the mouse button is released but does get drawn and blinks...it's annoying. :/
Apparently I forgot to actually ask my question...How would one go about getting rid of the selection without completely handling the drawing?
In my experience you usually can't. Either you draw the item yourself or you don't. If you try to composite your graphics on top of those drawn by the control, you'll end up with glitches.
It is a bit of a pain because you have to handle focus rectangles, selection highlights, and drawing all the glyphs yourself.
On the plus side, Visual Styles can be used to do most of the work.
Here's some code that will get you most of the way there (it's incomplete, in that it uses some methods not included, and it doesn't render exactly what a normal treeview does because it supports grad filled items and columns, but should be a handy reference)
protected virtual void OnDrawTreeNode(object sender, DrawTreeNodeEventArgs e)
{
string text = e.Node.Text;
Rectangle itemRect = e.Bounds;
if (e.Bounds.Height < 1 || e.Bounds.Width < 1)
return;
int cIndentBy = 19; // TODO - support Indent value
int cMargin = 6; // TODO - this is a bit random, it's slaved off the Indent in some way
int cTwoMargins = cMargin * 2;
int indent = (e.Node.Level * cIndentBy) + cMargin;
int iconLeft = indent; // Where to draw parentage lines & icon/checkbox
int textLeft = iconLeft + 16; // Where to draw text
Color leftColour = e.Node.BackColor;
Color textColour = e.Node.ForeColor;
if (Bitfield.IsBitSet(e.State, TreeNodeStates.Grayed))
textColour = Color.FromArgb(255,128,128,128);
// Grad-fill the background
Brush backBrush = new SolidBrush(leftColour);
e.Graphics.FillRectangle(backBrush, itemRect);
// Faint underline along the bottom of each item
Color separatorColor = ColourUtils.Mix(leftColour, Color.FromArgb(255,0,0,0), 0.02);
Pen separatorPen = new Pen(separatorColor);
e.Graphics.DrawLine(separatorPen, itemRect.Left, itemRect.Bottom-1, itemRect.Right, itemRect.Bottom-1);
// Bodged to use Button styles as Treeview styles not available on my laptop...
if (!HideSelection)
{
if (Bitfield.IsBitSet(e.State, TreeNodeStates.Selected) || Bitfield.IsBitSet(e.State, TreeNodeStates.Hot))
{
Rectangle selRect = new Rectangle(textLeft, itemRect.Top, itemRect.Right - textLeft, itemRect.Height);
VisualStyleRenderer renderer = new VisualStyleRenderer((ContainsFocus) ? VisualStyleElement.Button.PushButton.Hot
: VisualStyleElement.Button.PushButton.Normal);
renderer.DrawBackground(e.Graphics, selRect);
// Bodge to make VisualStyle look like Explorer selections - overdraw with alpha'd white rectangle to fade the colour a lot
Brush bodge = new SolidBrush(Color.FromArgb((Bitfield.IsBitSet(e.State, TreeNodeStates.Hot)) ? 224 : 128,255,255,255));
e.Graphics.FillRectangle(bodge, selRect);
}
}
Pen dotPen = new Pen(Color.FromArgb(128,128,128));
dotPen.DashStyle = DashStyle.Dot;
int midY = (itemRect.Top + itemRect.Bottom) / 2;
// Draw parentage lines
if (ShowLines)
{
int x = cMargin * 2;
if (e.Node.Level == 0 && e.Node.PrevNode == null)
{
// The very first node in the tree has a half-height line
e.Graphics.DrawLine(dotPen, x, midY, x, itemRect.Bottom);
}
else
{
TreeNode testNode = e.Node; // Used to only draw lines to nodes with Next Siblings, as in normal TreeViews
for (int iLine = e.Node.Level; iLine >= 0; iLine--)
{
if (testNode.NextNode != null)
{
x = (iLine * cIndentBy) + (cMargin * 2);
e.Graphics.DrawLine(dotPen, x, itemRect.Top, x, itemRect.Bottom);
}
testNode = testNode.Parent;
}
x = (e.Node.Level * cIndentBy) + cTwoMargins;
e.Graphics.DrawLine(dotPen, x, itemRect.Top, x, midY);
}
e.Graphics.DrawLine(dotPen, iconLeft + cMargin, midY, iconLeft + cMargin + 10, midY);
}
// Draw Expand (plus/minus) icon if required
if (ShowPlusMinus && e.Node.Nodes.Count > 0)
{
// Use the VisualStyles renderer to use the proper OS-defined glyphs
Rectangle expandRect = new Rectangle(iconLeft-1, midY - 7, 16, 16);
VisualStyleElement element = (e.Node.IsExpanded) ? VisualStyleElement.TreeView.Glyph.Opened
: VisualStyleElement.TreeView.Glyph.Closed;
VisualStyleRenderer renderer = new VisualStyleRenderer(element);
renderer.DrawBackground(e.Graphics, expandRect);
}
// Draw the text, which is separated into columns by | characters
Point textStartPos = new Point(itemRect.Left + textLeft, itemRect.Top);
Point textPos = new Point(textStartPos.X, textStartPos.Y);
Font textFont = e.Node.NodeFont; // Get the font for the item, or failing that, for this control
if (textFont == null)
textFont = Font;
StringFormat drawFormat = new StringFormat();
drawFormat.Alignment = StringAlignment.Near;
drawFormat.LineAlignment = StringAlignment.Center;
drawFormat.FormatFlags = StringFormatFlags.NoWrap;
string [] columnTextList = text.Split('|');
for (int iCol = 0; iCol < columnTextList.GetLength(0); iCol++)
{
Rectangle textRect = new Rectangle(textPos.X, textPos.Y, itemRect.Right - textPos.X, itemRect.Bottom - textPos.Y);
if (mColumnImageList != null && mColumnImageList[iCol] != null)
{
// This column has an imagelist assigned, so we use the column text as an integer zero-based index
// into the imagelist to indicate the icon to display
int iImage = 0;
try
{
iImage = MathUtils.Clamp(Convert.ToInt32(columnTextList[iCol]), 0, mColumnImageList[iCol].Images.Count);
}
catch(Exception)
{
iImage = 0;
}
e.Graphics.DrawImageUnscaled(mColumnImageList[iCol].Images[iImage], textRect.Left, textRect.Top);
}
else
e.Graphics.DrawString(columnTextList[iCol], textFont, new SolidBrush(textColour), textRect, drawFormat);
textPos.X += mColumnWidthList[iCol];
}
// Draw Focussing box around the text
if (e.State == TreeNodeStates.Focused)
{
SizeF size = e.Graphics.MeasureString(text, textFont);
size.Width = (ClientRectangle.Width - 2) - textStartPos.X;
size.Height += 1;
Rectangle rect = new Rectangle(textStartPos, size.ToSize());
e.Graphics.DrawRectangle(dotPen, rect);
// ControlPaint.DrawFocusRectangle(e.Graphics, Rect);
}
}