How do I add a button beside each node of a TreeView?
Adding a button beside each node of a treeview is difficult. You would have to handle drawing of the treeview yourself, and either draw the buttons yourself and emulate their functionality, or create child button controls and display them in the right places within the tree control and then handle repositioning them when the control scrolls, etc. Either way it is going to be a nightmare.
Luckily, there is an easy way out: you do not have to do any of that complicated stuff, BECAUSE YOU SHOULD NOT DO THEM!
Have you ever seen a tree control with buttons in it? No. Therefore, if your tree control has buttons in it, it will be seen by your end users as bizarre.
What you should do is consider how other applications have solved the problem that you are trying to solve without using tree controls with buttons in them, and do as they do.
The simplest way to do this is to draw tree yourself. Here is a small example (please note that PushButtonState is located inside System.Windows.Forms.VisualStyles namespace):
public class CustomTreeView : TreeView
{
private Rectangle buttonRect = new Rectangle(80, 2, 50, 26);
private StringFormat stringFormat;
public CustomTreeView()
{
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
DrawMode = TreeViewDrawMode.OwnerDrawText;
ShowLines = false;
FullRowSelect = true;
ItemHeight = 30;
stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Near;
stringFormat.LineAlignment = StringAlignment.Center;
}
protected override void OnDrawNode(DrawTreeNodeEventArgs e)
{
e.Graphics.DrawString(e.Node.Text, this.Font, new SolidBrush(this.ForeColor), e.Bounds, stringFormat);
ButtonRenderer.DrawButton(e.Graphics, new Rectangle(e.Node.Bounds.Location + new Size(buttonRect.Location), buttonRect.Size), "btn", this.Font, true, (e.Node.Tag != null) ? (PushButtonState)e.Node.Tag : PushButtonState.Normal);
}
protected override void OnNodeMouseClick(TreeNodeMouseClickEventArgs e)
{
if (e.Node.Tag != null && (PushButtonState)e.Node.Tag == PushButtonState.Pressed)
{
e.Node.Tag = PushButtonState.Normal;
MessageBox.Show(e.Node.Text + " clicked");
// force redraw
e.Node.Text = e.Node.Text;
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
TreeNode tnode = GetNodeAt(e.Location);
if (tnode == null) return;
Rectangle btnRectAbsolute = new Rectangle(tnode.Bounds.Location + new Size(buttonRect.Location), buttonRect.Size);
if (btnRectAbsolute.Contains(e.Location))
{
tnode.Tag = PushButtonState.Pressed;
tnode.Text = tnode.Text;
}
}
}
Also, you can achieve this even without creating custom control - just add these event handlers to standard TreeView
Related
this is for Winforms / C# btw.
So I am making my own ComboBox, just playing around.
The item List is basically a Panel, which is located under or above the ComboBox control, depending on its height and location.
This works as intended, as long as I use AnchorStyle Top / Left for my usercontrol.
As soon as I switch to Top / Right Style, the location of my listpanel breaks. Okay, makes sense, the location point of my usercontrol changes with the different anchorstyle, so how about I adjust my panel to a new location using my usercontrol.LocationChanged event? Doesen't work. Maybe because of the order of the events? Or does the origin of locations change depending on the given AnchorStyles?
Anyway, I am a bit lost. Unfortunately I couldn't find a similar case here, therefore this question.
Here's my DropDownPanel (effectively my "list"):
private Panel DropDownPanel()
{
Panel DropDownPanel = new Panel() {
Width = this.Width,
Height = 200,
BackColor = _skin.TextBoxBackColor,
BorderStyle = BorderStyle.FixedSingle,
ForeColor = _skin.TextBoxForeColor,
Visible = false,
AutoScroll = false
};
DropDownPanel.HorizontalScroll.Enabled = false;
DropDownPanel.HorizontalScroll.Visible = false;
DropDownPanel.HorizontalScroll.Maximum = 0;
DropDownPanel.AutoScroll = true;
DropDownPanel.Leave += DropDownPanel_Leave;
return DropDownPanel;
}
Here's my userControl Load Event:
private void GbComboBox_Load(object sender, EventArgs e)
{
_dropDownPanel = DropDownPanel();
this.Parent.Parent.Controls.Add(_dropDownPanel);
RelocateDropDownPanel();
_dropDownPanel.BringToFront();
ApplyItems(_items);
}
And my calculation for the location:
private void RelocateDropDownPanel()
{
if (_dropDownPanel != null)
{
int initLocY = this.Parent.Location.Y + this.Location.Y + this.Height + _dropDownPanel.Height;
int fullHeight = this.FindForm().Height;
Point p = new Point();
if (initLocY < fullHeight)
{
p = new Point(this.Parent.Location.X + this.Location.X, initLocY - _dropDownPanel.Height);
}
else
{
p = new Point(this.Parent.Location.X + this.Location.X, initLocY - _dropDownPanel.Height - this.Height);
}
_dropDownPanel.Location = p;
}
}
So I figured out a "solution".
I simply inherited the Anchor of my Panel from the Usercontrol it was associated to.
But I wanna mention, that still, this whole thing is kind of a crappy way of dealing with custom combobox desings. As #Jimi stated in his first comment, the correct way of doing this would be to ownerdraw the combobox. I haven't done this because of specific reasons, which didn't let me do this.
A little example on how this could be made:
Change ComboBox Border Color in Windows Forms
I've derived the ComboBox Class in order to have a custom layout of the list items. This is a DropDown list style Combobox, which has a nice appearance, while the custom one looks like an old-style one.
Is there any way to emulate or force the control to draw the "windows default-style" layout while still having a custom design? This is, having a style similar to the right one on the picture above, with the gradient background (or whatever it takes depending on the version of windows).
This what I've done so far:
public ComboBoxRGB()
{
this.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
}
protected override void OnDrawItem(System.Windows.Forms.DrawItemEventArgs e)
{
base.OnDrawItem(e);
if (Items.Count > 0)
{
if (e.Index > -1)
{
if (Items[e.Index] == null) return;
int BarWidth = 16;
int BarSpacing = 1;
int Spacing_Top = 2;
int Spacing_Left = 3;
int Spacing_Right = 4;
ComboBoxRGBItem item;
try
{
item = (ComboBoxRGBItem)Items[e.Index];
}
catch { return; }
e.DrawBackground();
e.DrawFocusRectangle();
//Draw color indicator
System.Drawing.SolidBrush bColor = new System.Drawing.SolidBrush(System.Drawing.Color.FromArgb(item.Red, item.Green, item.Blue));
e.Graphics.FillRectangle(bColor, Spacing_Left + e.Bounds.Left, e.Bounds.Top + Spacing_Top, 12, 12);
//Draw text
System.Drawing.SolidBrush sbText = (e.State == System.Windows.Forms.DrawItemState.Selected) ? new System.Drawing.SolidBrush(System.Drawing.Color.White) : new System.Drawing.SolidBrush(System.Drawing.Color.Black);
e.Graphics.DrawString(item.Text, e.Font, sbText, Spacing_Left + e.Bounds.Left + BarWidth + Spacing_Right, e.Bounds.Top);
} //IF_Index
} //IF_Items_Count
}
I would be very pleased if anyone has any idea on how to do this. I would like to try to emulate the appearance manually, since this changes from windows version to version.
I was not able to find information about this on internet, so I suppose is not possible to auto-draw the backgound using system-default-style.
For the moment, the solution used is to draw it completely manually, through the override of Paint method, but this requires to take into account what is the version of Windows being executed and use different designs for drawing the control.
In case anyone has a different solution or any suggestion, please, let me know it.
I'm using validation methods for my textboxes in a class named Validators. I'm trying also to draw a rectangle on the textbox which failed to validate.
Im using this code:
private void TextBoxStyle(TextBox textBox)
{
Graphics graphics = textBox.CreateGraphics();
Pen redPen = new Pen(Color.Red);
graphics.DrawRectangle(redPen, textBox.Location.X, textBox.Location.Y, textBox.Width, textBox.Height);
}
/// <summary>
/// Validates TextBoxes for string input.
/// </summary>
public bool ValidateTextBoxes(params TextBox[] textBoxes)
{
foreach (var textBox in textBoxes)
{
if (textBox.Text.Equals(""))
{
Graphics graphics = textBox.CreateGraphics();
Pen redPen = new Pen(Color.Red);
graphics.DrawRectangle(redPen, textBox.Location.X, textBox.Location.Y, textBox.Width, textBox.Height);
return false;
}
}
return true;
}
The problem is... the rectangles wont show. Am I doing something wrong with the code ? If yes, help please.
A couple potential problems I see:
You get the Graphics object for the text box but use the textbox's offset in the form to do the drawing. Net result: the rectangle is translated outside the visible area of the textbox. Try using the location (0,0).
You draw the rectangle as wide as the textbox. Net result: right and bottom edges won't be visible. You should subtract the width of the pen from these values.
While you're at it, check out the ErrorProvider class. It may just take care of your needs off-the-shelf.
write a user control
public partial class UserControl1 : UserControl
{
private string text;
private bool isvalid = true;
public string Text
{
get { return textBox.Text; }
set { textBox.Text = value; }
}
public bool isValid
{
set
{
isvalid = value;
this.Refresh();
}
}
TextBox textBox = new TextBox();
public UserControl1()
{
InitializeComponent();
this.Paint += new PaintEventHandler(UserControl1_Paint);
this.Resize += new EventHandler(UserControl1_Resize);
textBox.Multiline = true;
textBox.BorderStyle = BorderStyle.None;
this.Controls.Add(textBox);
}
private void UserControl1_Resize(object sender, EventArgs e)
{
textBox.Size = new Size(this.Width - 3, this.Height - 2);
textBox.Location = new Point(2, 1);
}
private void UserControl1_Paint(object sender, PaintEventArgs e)
{
if (isvalid)
ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
else
ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle, Color.Red, ButtonBorderStyle.Solid);
}
}
update:
just added the isvalid property
you can put properties to show the border or not. if the input is valid show normal border and if the control input is invalid show the red border.
Anything drawn directly onto the TextBox will disappear as soon as the TextBox control is invalidated in some way.
A correct approach is to add a User Control to your project and add a TextBox on its canvas. Leave a little border around it.
You can now simply color the background of the user control's canvas red when needed and it will look like a border drawn around the TextBox.
You can add code directly to the user control to validate it whenever the text changes. That way, you only have to write code once and just add as many TextBoxes as you need to your forms or pages.
You shouldn't paint on a control simply from somewhere. The build in painting will override it on the next occasion. The Control has a paint event where you should paint. That will be used whenever painting is needed.
In your validate method you should just store the result of the validation somewhere so that it can be used in the paint event and call Invalidate() so that a repainting is enforced.
// You may use this
Label lblHighlight = new Label ();
Rectangle rc = new Rectangle(this.Left - 2, this.Top - 2, this.Width + 4, this.Bottom - this.Top + 4);
this.Parent.Controls.Add(lblHighlight);
lblHighlight.Bounds = rc;
lblHighlight.BackColor = "Red";
I'm displaying a set of search results in a ListView. The first column holds the search term, and the second shows the number of matches.
There are tens of thousands of rows, so the ListView is in virtual mode.
I'd like to change this so that the second column shows the matches as hyperlinks, in the same way as a LinkLabel shows links; when the user clicks on the link, I'd like to receive an event that will let me open up the match elsewhere in our application.
Is this possible, and if so, how?
EDIT: I don't think I've been sufficiently clear - I want multiple hyperlinks in a single column, just as it is possible to have multiple hyperlinks in a single LinkLabel.
You can easily fake it. Ensure that the list view items you add have UseItemStyleForSubItems = false so that you can set the sub-item's ForeColor to blue. Implement the MouseMove event so you can underline the "link" and change the cursor. For example:
ListViewItem.ListViewSubItem mSelected;
private void listView1_MouseMove(object sender, MouseEventArgs e) {
var info = listView1.HitTest(e.Location);
if (info.SubItem == mSelected) return;
if (mSelected != null) mSelected.Font = listView1.Font;
mSelected = null;
listView1.Cursor = Cursors.Default;
if (info.SubItem != null && info.Item.SubItems[1] == info.SubItem) {
info.SubItem.Font = new Font(info.SubItem.Font, FontStyle.Underline);
listView1.Cursor = Cursors.Hand;
mSelected = info.SubItem;
}
}
Note that this snippet checks if the 2nd column is hovered, tweak as needed.
Use ObjectListView -- an open source wrapper around a standard ListView. It supports links directly:
This recipe documents the (very simple) process and how you can customise it.
The other answers here are great, but if you don't want to have to hack some code together, look at the DataGridView control which has support for LinkLabel equivalent columns.
Using this control, you get all the functionality of the details view in a ListView, but with more customisation per row.
You can by inheriting the ListView control override the method OnDrawSubItem.
Here is a VERY simple example of how you might do:
public class MyListView : ListView
{
private Brush m_brush;
private Pen m_pen;
public MyListView()
{
this.OwnerDraw = true;
m_brush = new SolidBrush(Color.Blue);
m_pen = new Pen(m_brush)
}
protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
}
protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
{
if (e.ColumnIndex != 1) {
e.DrawDefault = true;
return;
}
// Draw the item's background.
e.DrawBackground();
var textSize = e.Graphics.MeasureString(e.SubItem.Text, e.SubItem.Font);
var textY = e.Bounds.Y + ((e.Bounds.Height - textSize.Height) / 2);
int textX = e.SubItem.Bounds.Location.X;
var lineY = textY + textSize.Height;
// Do the drawing of the underlined text.
e.Graphics.DrawString(e.SubItem.Text, e.SubItem.Font, m_brush, textX, textY);
e.Graphics.DrawLine(m_pen, textX, lineY, textX + textSize.Width, lineY);
}
}
You can set HotTracking to true so that when the user hovers mouse over the item it appears as link.
Is there a way to disable or better yet draw your own focus rectangle for a regular button control! (that dotted line seems so Windowss 95ish)
I've noticed that the control properties (FOR BUTTONS) does not have a ownerdrawfixed setting (which I don't know if that's even the route to use for the solution, although i've seen it used for customizing other controls).
Getting this right is trickier than it sounds. No doubt one of the reasons that custom button painting isn't overridable. This worked as expected:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
class MyButton : Button {
private VisualStyleRenderer renderer;
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
if (this.Focused && Application.RenderWithVisualStyles && this.FlatStyle == FlatStyle.Standard) {
if (renderer == null) {
VisualStyleElement elem = VisualStyleElement.Button.PushButton.Normal;
renderer = new VisualStyleRenderer(elem.ClassName, elem.Part, (int)PushButtonState.Normal);
}
Rectangle rc = renderer.GetBackgroundContentRectangle(e.Graphics, new Rectangle(0, 0, this.Width, this.Height));
rc.Height--;
rc.Width--;
using (Pen p = new Pen(Brushes.DarkGray)) {
e.Graphics.DrawRectangle(p, rc);
}
}
}
}
A quick and easy way to disable the focus rectangle all together is to subclass the control and include this code:
public class CustomButton : Button
{
protected override bool ShowFocusCues
{
get
{
return false;
}
}
}
Just simple way.
Set
button.FlatStyle = Flat;
button.FlatAppearance.BorderColor = Color.FromArgb(0, 255, 255, 255);
button.FlatAppearance.BorderSize = 0;
button.TabStop = false;
FlatAppearance.BorderColor
set on code cause could not transparent color set in design mode.
Subclass the Button class and override OnPaint. If your override does not call base.OnPaint, nothing will be drawn for the button and you will have complete control (including the focus rectangle).
One quick/dirty solution that I found (for removing the focus rectangle only) was explicitly defining the background color. For the default control color, for ex:
this._dropDownButton.BackColor = System.Drawing.ColorTranslator.FromHtml("#F0F0F0");
EDIT: Apparently this doesn't work. It was being fixed in my case for an unrelated reason. Apologies.
I had the same issue when using BackgroundImage to set an image on the button. When the user pressed 'Tab', my image button got a black rectangle.
The solution that worked for me is:
Call for NotifyDefault(false) for every button I used.
Set the TabStop property to false for every button I used.
Tested on .NET Framework 4.6.2.
Before:
After:
In my case, I have to use both solutions above to make it work.
public class ButtonNoFocus : Button
{
protected override bool ShowFocusCues
{
get
{
return false;
}
}
public override void NotifyDefault(bool value)
{
base.NotifyDefault(false);
}
}
A simple solution to hide the focus frame would be to switch the focus from buttons to a dummy control as soon as the button has been clicked:
public frmMain()
{
...
RemoveControlFocusFrame(this);
}
private void RemoveControlFocusFrame(Control c)
{
if (c.Controls.Count == 0)
{
if (c is Button || c is CheckBox)
c.GotFocus += (o, e) => lblFocusDump.Focus();
return;
}
foreach (Control sub in c.Controls)
RemoveControlFocusFrame(sub);
}
The dummy lblFocusDump label has its Visible set to true and can be hidden in Designer by pushing it to background behind any other control.