I am overriding a TreeView so that I can highlight the nodes using better colors. As part of the applications options I want to enable the user to change the TreeView's font and the font color when both selected and deselected. The code is below:
class MyTreeView : TreeView
{
// Create a Font object for the node tags and HotTracking.
private Font hotFont;
private Font tagFont = new Font("Helvetica", Convert.ToSingle(8.0), FontStyle.Bold);
#region Accessors.
public Font hotTrackFont
{
get { return this.hotFont; }
set { this.hotFont = value; }
}
//public string unFocusedColor
//{
// get { return this.strDeselectedColor; }
// set { this.strDeselectedColor = value; }
//}
//public string focusedColor
//{
// get { return this.strSelectedColor; }
// set { this.strSelectedColor = value; }
//}
#endregion
public MyTreeView()
{
this.HotTracking = true;
this.DrawMode = TreeViewDrawMode.OwnerDrawText;
hotFont = new Font(this.Font.FontFamily, this.Font.Size, FontStyle.Underline);
}
// Override the drawMode of TreeView.
protected override void OnDrawNode(DrawTreeNodeEventArgs e)
{
TreeNodeStates treeState = e.State;
Font treeFont = e.Node.NodeFont ?? e.Node.TreeView.Font;
// Colors.
Color foreColor = e.Node.ForeColor;
// Like with the hotFont I want to be able to change these dynamically...
string strDeselectedColor = #"#6B6E77", strSelectedColor = #"#94C7FC";
Color selectedColor = System.Drawing.ColorTranslator.FromHtml(strSelectedColor);
Color deselectedColor = System.Drawing.ColorTranslator.FromHtml(strDeselectedColor);
// New brush.
SolidBrush selectedTreeBrush = new SolidBrush(selectedColor);
SolidBrush deselectedTreeBrush = new SolidBrush(deselectedColor);
// Set default font color.
if (foreColor == Color.Empty)
foreColor = e.Node.TreeView.ForeColor;
// Draw bounding box and fill.
if (e.Node == e.Node.TreeView.SelectedNode)
{
// Use appropriate brush depending on if the tree has focus.
if (this.Focused)
{
foreColor = SystemColors.HighlightText;
e.Graphics.FillRectangle(selectedTreeBrush, e.Bounds);
ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds, foreColor, SystemColors.Highlight);
TextRenderer.DrawText(e.Graphics, e.Node.Text, treeFont, e.Bounds,
foreColor, TextFormatFlags.GlyphOverhangPadding);
}
else
{
foreColor = SystemColors.HighlightText;
e.Graphics.FillRectangle(deselectedTreeBrush, e.Bounds);
ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds, foreColor, SystemColors.Highlight);
TextRenderer.DrawText(e.Graphics, e.Node.Text, treeFont, e.Bounds,
foreColor, TextFormatFlags.GlyphOverhangPadding);
}
}
else
{
if ((e.State & TreeNodeStates.Hot) == TreeNodeStates.Hot)
{
e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
TextRenderer.DrawText(e.Graphics, e.Node.Text, hotFont, e.Bounds,
System.Drawing.Color.Black, TextFormatFlags.GlyphOverhangPadding);
}
else
{
e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
TextRenderer.DrawText(e.Graphics, e.Node.Text, treeFont, e.Bounds,
foreColor, TextFormatFlags.GlyphOverhangPadding);
}
}
}
}
As you can see I am currently changing the Font hotFont using an accessor to the class which seems to work. However, when I try to edit the colors of the focused/unfocused node, VS2010 crashes! What is it that is causing this behaviour exactly and how can I achieve what I want?
The posted code does not recreate the error when I enabled those commented out properties and added the variables to the control's scope.
A couple of notes though:
Instead of string properties, I would use an actual color:
private Color _UnfocusedColor = ColorTranslator.FromHtml(#"#94C7FC");
private Color _FocusedColor = ColorTranslator.FromHtml(#"#6B6E77");
public Color UnfocusedColor
{
get { return _UnfocusedColor; }
set { _UnfocusedColor = value; }
}
public Color FocusedColor
{
get { return _FocusedColor; }
set { _FocusedColor = value; }
}
Also, make sure to dispose of your drawing objects, such as the SolidBrush objects, or wrap them up in a Using(...){} block.
Related
I have a treeview with some nodes. Under some condition I want to color each node with different color along with their children. I have written a function that colors node and its children.
Will anyone please let me know is there any possibility that I have Color.Green as a variable such that I wont write the whole function for each color? I mean as an input parameter in function.
Here is the function:
public void ColorChild(TreeNode nodes, int indx)
{
foreach (TreeNode node_tmp in nodes.Nodes[indx].Nodes)
{
System.Drawing.Color = Green;
node_tmp.ForeColor = color;
foreach (TreeNode node_tmp2 in node_tmp.Nodes)
{
node_tmp2.ForeColor = Color.Green;
foreach (TreeNode node_tmp3 in node_tmp2.Nodes)
{
node_tmp3.ForeColor = Color.Green;
foreach (TreeNode node_tmp4 in node_tmp3.Nodes)
{
node_tmp4.ForeColor = Color.Green;
foreach (TreeNode node_tmp5 in node_tmp4.Nodes)
{
node_tmp5.ForeColor = Color.Green;
}
}
}
}
}
}
You could use recursion to iterate through all your nodes.
Something like:
void ColorNode(TreeNodeCollection nodes, System.Drawing.Color Color)
{
foreach (TreeNode child in nodes)
{
child.ForeColor= Color;
if(child.Nodes != null && child.Nodes.Count>0)
ColorNode(child.Nodes, Color);
}
}
And call it from you method like:
public void ColorChild(TreeNode nodes, int indx)
{
ColorNode(nodes.Nodes, Color.Green);
}
Your solution is extreme slow if the tree is large. Just color the nodes on demand when you draw them. To do so you need to set the drawing mode:
treeView1.DrawMode = TreeViewDrawMode.OwnerDrawText;
And then, when a node is about to drawn, ask its color on demand:
private void tree_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
TreeNodeStates state = e.State;
Font font = e.Node.NodeFont ?? e.Node.TreeView.Font;
Color foreColor;
Color backColor;
// node is selected
// if you want to see the color of a selected node, too,
// you can use inverted fore/back colors instead of system selection colors
if ((state & TreeNodeStates.Selected) == TreeNodeStates.Selected)
{
bool isFocused = (state & TreeNodeStates.Focused) == TreeNodeStates.Focused;
backColor = SystemColors.Highlight;
foreColor = isFocused ? SystemColors.HighlightText : SystemColors.InactiveCaptionText;
e.Graphics.FillRectangle(SystemBrushes.Highlight, e.Bounds);
if (isFocused)
ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds, foreColor, backColor);
TextRenderer.DrawText(e.Graphics, e.Node.Text, font, e.Bounds, foreColor, TextFormatFlags.GlyphOverhangPadding | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis | TextFormatFlags.NoPrefix);
}
// node is not selected
else
{
backColor = GetBackColor(e.Node); // GetBackColor: return some color by condition
foreColor = GetForeColor(e.Node); // GetForeColor: return some color by condition
using (Brush background = new SolidBrush(backColor))
{
e.Graphics.FillRectangle(background, e.Bounds);
TextRenderer.DrawText(e.Graphics, e.Node.Text, font, e.Bounds, foreColor, TextFormatFlags.GlyphOverhangPadding | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis);
}
}
}
If conditions are changed just invalidate the tree:
treeView1.Invalidate(); // this will re-draw the visible nodes
Well, if you want Color as a parameter to your function, there is nothing stopping you.
public void ColorChild(TreeNode nodes, int indx, Color color)
{
foreach (TreeNode node_tmp in nodes.Nodes[indx].Nodes)
{
node_tmp.ForeColor = color;
foreach (TreeNode node_tmp2 in node_tmp.Nodes)
{
node_tmp2.ForeColor = color;
foreach (TreeNode node_tmp3 in node_tmp2.Nodes)
{
node_tmp3.ForeColor = color;
foreach (TreeNode node_tmp4 in node_tmp3.Nodes)
{
node_tmp4.ForeColor = color;
foreach (TreeNode node_tmp5 in node_tmp4.Nodes)
{
node_tmp5.ForeColor = color;
}
}
}
}
}
}
Then just call it like
ColorChild(nodes, indx, Color.Green);
I've got a custom control with two PictureBox controls that are animated and a label control over them.
The child indexes are set so that label is always on top but the picture boxes are interchanging so when animated they display different images each time.
As I understand, label needs to have a parent control on top of which it can support a semi transparent color (Argb). Since the label has active picture box as its parent it will also be animated with which is not what I want at all.
Is there a way to fix a child position relative to parents parent?
To have a transparent label control, you can override the OnPaint method and draw all controls that intersects with label, at last draw the background and text of the label.
Also when moving your picture boxes, don't forget to call the Invalidate() method of the transparent label.
Screenshot
Sample Implementation
public class TransparentLabel : Label
{
public TransparentLabel()
{
this.transparentBackColor = Color.Blue;
this.opacity = 50;
this.BackColor = Color.Transparent;
}
protected override void OnPaint(PaintEventArgs e)
{
if (Parent != null)
{
using (var bmp = new Bitmap(Parent.Width, Parent.Height))
{
Parent.Controls.Cast<Control>()
.Where(c => Parent.Controls.GetChildIndex(c) > Parent.Controls.GetChildIndex(this))
.Where(c => c.Bounds.IntersectsWith(this.Bounds))
.OrderByDescending(c => Parent.Controls.GetChildIndex(c))
.ToList()
.ForEach(c => c.DrawToBitmap(bmp, c.Bounds));
e.Graphics.DrawImage(bmp, -Left, -Top);
using (var b = new SolidBrush(Color.FromArgb(this.Opacity, this.TransparentBackColor)))
{
e.Graphics.FillRectangle(b, this.ClientRectangle);
}
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
TextRenderer.DrawText(e.Graphics, this.Text, this.Font, this.ClientRectangle, this.ForeColor, Color.Transparent);
}
}
}
private int opacity;
public int Opacity
{
get { return opacity; }
set
{
if (value >= 0 && value <= 255)
opacity = value;
this.Invalidate();
}
}
public Color transparentBackColor;
public Color TransparentBackColor
{
get { return transparentBackColor; }
set
{
transparentBackColor = value;
this.Invalidate();
}
}
[Browsable(false)]
public override Color BackColor
{
get
{
return Color.Transparent;
}
set
{
base.BackColor = Color.Transparent;
}
}
}
I have been playing around with customizing the WinForm combobox...So far I have the following:
Using this code:
public class ComboBoxWithBorder : ComboBox
{
private Color _borderColor = Color.Black;
private ButtonBorderStyle _borderStyle = ButtonBorderStyle.Solid;
private static int WM_PAINT = 0x000F;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
Graphics g = Graphics.FromHwnd(Handle);
Rectangle bounds = new Rectangle(0, 0, Width, Height);
ControlPaint.DrawBorder(g, bounds, _borderColor, _borderStyle);
}
}
[Category("Appearance")]
public Color BorderColor
{
get { return _borderColor; }
set
{
_borderColor = value;
Invalidate(); // causes control to be redrawn
}
}
[Category("Appearance")]
public ButtonBorderStyle BorderStyle
{
get { return _borderStyle; }
set
{
_borderStyle = value;
Invalidate();
}
}
}
However, I am trying to achieve something similar to this:
Is it possible to change the background color of the white dropdown box to a darker color?
Is it possible to change the dropdown list border from white to a different color?
Try This Class
Refer
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class FlatCombo : ComboBox
{
private Brush BorderBrush = new SolidBrush(SystemColors.Window);
private Brush ArrowBrush = new SolidBrush(SystemColors.ControlText);
private Brush DropButtonBrush = new SolidBrush(SystemColors.Control);
private Color _ButtonColor = SystemColors.Control;
public Color ButtonColor {
get { return _ButtonColor; }
set {
_ButtonColor = value;
DropButtonBrush = new SolidBrush(this.ButtonColor);
this.Invalidate();
}
}
protected override void WndProc(ref Message m)
{
base.WndProc(m);
switch (m.Msg) {
case 0xf:
//Paint the background. Only the borders
//will show up because the edit
//box will be overlayed
Graphics g = this.CreateGraphics;
Pen p = new Pen(Color.White, 2);
g.FillRectangle(BorderBrush, this.ClientRectangle);
//Draw the background of the dropdown button
Rectangle rect = new Rectangle(this.Width - 15, 3, 12, this.Height - 6);
g.FillRectangle(DropButtonBrush, rect);
//Create the path for the arrow
Drawing2D.GraphicsPath pth = new Drawing2D.GraphicsPath();
PointF TopLeft = new PointF(this.Width - 13, (this.Height - 5) / 2);
PointF TopRight = new PointF(this.Width - 6, (this.Height - 5) / 2);
PointF Bottom = new PointF(this.Width - 9, (this.Height + 2) / 2);
pth.AddLine(TopLeft, TopRight);
pth.AddLine(TopRight, Bottom);
g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality;
//Determine the arrow's color.
if (this.DroppedDown) {
ArrowBrush = new SolidBrush(SystemColors.HighlightText);
} else {
ArrowBrush = new SolidBrush(SystemColors.ControlText);
}
//Draw the arrow
g.FillPath(ArrowBrush, pth);
break;
default:
break; // TODO: might not be correct. Was : Exit Select
break;
}
}
//Override mouse and focus events to draw
//proper borders. Basically, set the color and Invalidate(),
//In general, Invalidate causes a control to redraw itself.
#region "Mouse and focus Overrides"
protected override void OnMouseEnter(System.EventArgs e)
{
base.OnMouseEnter(e);
BorderBrush = new SolidBrush(SystemColors.Highlight);
this.Invalidate();
}
protected override void OnMouseLeave(System.EventArgs e)
{
base.OnMouseLeave(e);
if (this.Focused)
return;
BorderBrush = new SolidBrush(SystemColors.Window);
this.Invalidate();
}
protected override void OnLostFocus(System.EventArgs e)
{
base.OnLostFocus(e);
BorderBrush = new SolidBrush(SystemColors.Window);
this.Invalidate();
}
protected override void OnGotFocus(System.EventArgs e)
{
base.OnGotFocus(e);
BorderBrush = new SolidBrush(SystemColors.Highlight);
this.Invalidate();
}
protected override void OnMouseHover(System.EventArgs e)
{
base.OnMouseHover(e);
BorderBrush = new SolidBrush(SystemColors.Highlight);
this.Invalidate();
}
#endregion
}
I have code below. How can i set checkedListBox item fore colour depending on if item is checked or not checked?
private void FindSelectedUserRoles()
{
lblSelectedUser.Text = Code.CommonUtilities.getDgvStringColValue(dataGridViewUserList, "UserName").Trim();
//iterate all roles selected user is member of
for (int i = 0; i < checkedListRoles.Items.Count; i++)
{
string roleName = checkedListRoles.Items[i].ToString();
string selectedUserRoles = Code.MemberShipManager.GetSpecificUsersRoles(lblSelectedUser.Text.Trim());
if (selectedUserRoles.Contains(roleName))
{
checkedListRoles.SetItemChecked(i, true);
//here i want to set item fore colour to green
}
else if (selectedUserRoles.Contains(roleName) == false)
{
checkedListRoles.SetItemChecked(i, false);
//and here, i want item fore colour to remain black
}
}
}
Since it's rather complicated to draw the thing yourself, you could actually let the original control draw itself -- just tweaking the color. This is my suggestion:
public class CustomCheckedListBox : CheckedListBox
{
protected override void OnDrawItem(DrawItemEventArgs e)
{
Color foreColor;
if (e.Index >= 0)
{
foreColor = GetItemChecked(e.Index) ? Color.Green : Color.Red;
}
else
{
foreColor = e.ForeColor;
}
// Copy the original event args, just tweaking the fore color.
var tweakedEventArgs = new DrawItemEventArgs(
e.Graphics,
e.Font,
e.Bounds,
e.Index,
e.State,
foreColor,
e.BackColor);
// Call the original OnDrawItem, but supply the tweaked color.
base.OnDrawItem(tweakedEventArgs);
}
}
I think you have to draw your own CheckedListBox item like this:
public class CustomCheckedListBox : CheckedListBox
{
public CustomCheckedListBox()
{
DoubleBuffered = true;
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
Size checkSize = CheckBoxRenderer.GetGlyphSize(e.Graphics, System.Windows.Forms.VisualStyles.CheckBoxState.MixedNormal);
int dx = (e.Bounds.Height - checkSize.Width)/2;
e.DrawBackground();
bool isChecked = GetItemChecked(e.Index);//For some reason e.State doesn't work so we have to do this instead.
CheckBoxRenderer.DrawCheckBox(e.Graphics, new Point(dx, e.Bounds.Top + dx), isChecked ? System.Windows.Forms.VisualStyles.CheckBoxState.CheckedNormal : System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal);
using (StringFormat sf = new StringFormat { LineAlignment = StringAlignment.Center })
{
using (Brush brush = new SolidBrush(isChecked ? CheckedItemColor : ForeColor))
{
e.Graphics.DrawString(Items[e.Index].ToString(), Font, brush, new Rectangle(e.Bounds.Height, e.Bounds.Top, e.Bounds.Width - e.Bounds.Height, e.Bounds.Height), sf);
}
}
}
Color checkedItemColor = Color.Green;
public Color CheckedItemColor
{
get { return checkedItemColor; }
set
{
checkedItemColor = value;
Invalidate();
}
}
}
If you want to set CheckedColor differently for each item, you have to store the CheckedColor setting for each item (such as in a Collection) and reference the CheckedColor using Index. However I think it's a little much work to do. So if you have such a requirement, going for ListView instead would be better.
I think you should try ListView instead of checkedListBox. It has necessary properties and could be customized as you wish. Just set Checkboxes property to true, and then in your code add forecolor like that:
listView1.Items[i].ForeColor = Color.Red;
Expanding on #Mattias' answer, I made this custom control to fit my needs. I needed it to have colors depending on other factors than the Checked value.
public class CheckedListBoxColorable : CheckedListBox
{
/// <summary>
/// Controls the forecolors of the objects in the Items collection.
/// If the item is not represented, it will have the default forecolor.
/// </summary>
public Dictionary<object, Color> Colors { get; set; }
public CheckedListBoxColorable()
{
this.DoubleBuffered = true; //prevent flicker, not sure if this is necessary.
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
//Default forecolor
Color foreColor = e.ForeColor;
//Item to be drawn
object item = null;
if (e.Index >= 0) //If index is -1, no customization is necessary
{
//Find the item to be drawn
if (this.Items.Count > e.Index) item = this.Items[e.Index];
//If the item was found and we have a color for it, get the custom forecolor
if (item != null && this.Colors != null && this.Colors.ContainsKey(item))
{
foreColor = this.Colors[item];
}
}
// Copy the original event args, just tweaking the forecolor.
var tweakedEventArgs = new DrawItemEventArgs(
e.Graphics,
e.Font,
e.Bounds,
e.Index,
e.State,
foreColor,
e.BackColor);
// Call the original OnDrawItem, but supply the tweaked color.
base.OnDrawItem(tweakedEventArgs);
}
}
Usage:
//Set the colors I want for my objects
foreach (var obj in objects)
{
//Add your own logic here to set the color depending on whatever criteria you have
if (obj.SomeProperty) lstBoxes.Colors.Add(obj, Color.Green);
else lstBoxes.Colors.Add(obj, Color.Red);
}
//Add the items to the checkedlistbox
lstBoxes.Items.AddRange(objects.ToArray());
The accepted answer worked for me but it needs modifying if you want to disable the CustomCheckedListBox.
I modified the code as follows: -
I changed the 'CheckBoxRenderer.DrawCheckBox...' line to
if(Enabled)
{
CheckBoxRenderer.DrawCheckBox(e.Graphics, new Point(dx, e.Bounds.Top + dx), isChecked ? System.Windows.Forms.VisualStyles.CheckBoxState.CheckedNormal : System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal);
}
else
{
CheckBoxRenderer.DrawCheckBox(e.Graphics, new Point(dx, e.Bounds.Top + dx), isChecked ? System.Windows.Forms.VisualStyles.CheckBoxState.CheckedDisabled : System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedDisabled);
}
and then I changed the 'using (Brush brush = new SolidBrush...' line to
using (Brush brush = new SolidBrush(isChecked ? CheckedItemColor : (Enabled ? ForeColor : SystemColors.GrayText)))
This caused enabling/disabling to work for me.
I have a TreeView and each of it's Node.Text has two words.
The first and second words should have different colors. I'm already changing the color of the text with the DrawMode properties and the DrawNode event but I can't figure out how to split the Node.Text in two different colors. Someone pointed out I could use TextRenderer.MeasureText but I have no idead how/where to use it.
Someone has an idea ?
Code :
formload()
{
treeView1.DrawMode = TreeViewDrawMode.OwnerDrawText;
}
private void treeView1_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
Color nodeColor = Color.Red;
if ((e.State & TreeNodeStates.Selected) != 0)
nodeColor = SystemColors.HighlightText;
TextRenderer.DrawText(e.Graphics,
e.Node.Text,
e.Node.NodeFont,
e.Bounds,
nodeColor,
Color.Empty,
TextFormatFlags.VerticalCenter);
}
Try this:
private void treeView1_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
string[] texts = e.Node.Text.Split();
using (Font font = new Font(this.Font, FontStyle.Regular))
{
using (Brush brush = new SolidBrush(Color.Red))
{
e.Graphics.DrawString(texts[0], font, brush, e.Bounds.Left, e.Bounds.Top);
}
using (Brush brush = new SolidBrush(Color.Blue))
{
SizeF s = e.Graphics.MeasureString(texts[0], font);
e.Graphics.DrawString(texts[1], font, brush, e.Bounds.Left + (int)s.Width, e.Bounds.Top);
}
}
}
You must manage State of node to do appropriated actions.
UPDATE
Sorry, my mistake see updated version. There is no necessary to measure space size because it already contains in texts[0].