how to avoid blank area treeview node clicking? - c#

I am working on a regular WinForm Outlook Addin and I have created a Treeview using DrawNode event, The tree is working as expected but there is a glitch in node clicking, only the green region is clickable and the half node gets non responsive.
When I use the MouseDown event the whole area gets clickable including the blank space right next to the node. But to restrict this blank clicking I am using a logic with the help of TreeViewHitTestLocations I am checking if the clicked location is the RightOfLabel then don't do anything but unfortunately this doesn't give me a precise result, it somehow gets confuse and takes right half of the label(Node) as the blank space and doesn't get clicked.
Note: I think this all happened because I played with DrawNode method and while keeping the distance between label and workspace icon the application underneath assumes that the label gets finish within the green portion so the red portion gets left as a blank space. This is just my assumption based on all the naïve things I have done with the method.
Need help to resolve this issue if someone can guide me to a fix. Thanks
void treeview_mousedown(object sender, MouseEventArgs e)
{
TreeNode nodeClicked;
// if arrow up/down will be excluded from the mousedown event
var hitTest = this.HitTest(e.Location);
if (hitTest.Location == TreeViewHitTestLocations.PlusMinus)
return;
if (hitTest.Location == TreeViewHitTestLocations.RightOfLabel)
return;
// Get the node clicked on
nodeClicked = this.GetNodeAt(e.X, e.Y);
// Was the node clicked on?
if (!(nodeClicked == null))
this.SelectedNode = nodeClicked;
}
Below is the treeview drawnode method I am using:
void treeview_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
Rectangle nodeRect = e.Node.Bounds;
// below location is the expand and collapse icon location
Point ptExpand = new Point(nodeRect.Location.X - 7, nodeRect.Location.Y + 5);
Image expandImg = null;
// check the below condition for nodes with child nodes and nodes without child nodes
if ( e.Node.Nodes.Count < 1)
expandImg = global::myresource.OfficeAddin.Controls.Resource.search;
else if (e.Node.IsExpanded && e.Node.Nodes.Count > 1)
expandImg = global::myresource.OfficeAddin.Controls.Resource.down_arrow_icon;
else
expandImg = global::myresource.OfficeAddin.Controls.Resource.right_arrow_icon;
Graphics g = Graphics.FromImage(expandImg);
IntPtr imgPtr = g.GetHdc();
g.ReleaseHdc();
e.Graphics.DrawImage(expandImg, ptExpand);
// draw node icon
Point ptNodeIcon = new Point(nodeRect.Location.X - 4, nodeRect.Location.Y + 2);
Image nodeImg = global::myresource.OfficeAddin.Controls.Resource.folder_icon_16px;
g = Graphics.FromImage(nodeImg);
imgPtr = g.GetHdc();
g.ReleaseHdc();
e.Graphics.DrawImage(nodeImg, ptNodeIcon);
// draw node text
Font nodeFont = e.Node.NodeFont;
if (e.Node.NodeFont != null)
{
nodeFont = e.Node.NodeFont;
} else {
nodeFont = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
}
// set the forecolor
Color forecolor = e.Node.ForeColor;
// color same as the font color
string strSelectedColor = #"#505050";
Color selectedColor = System.Drawing.ColorTranslator.FromHtml(strSelectedColor);
SolidBrush selectedTreeBrush = new SolidBrush(selectedColor);
//Inflate to not be cut
Rectangle textRect = nodeRect;
//below value controls the width of the text if given less then, long texts will come in multiple lines
textRect.Width += 150;
// below value controls the over all width of the node, if given less all the things will get sqeeze
e.Graphics.DrawString(e.Node.Text, nodeFont, selectedTreeBrush , Rectangle.Inflate(textRect, -20, 0));
}

Your assumption is right, you can check it by marking the bounds at drawing, for example:
TreeNodeStates state = e.State;
bool isFocused = (state & TreeNodeStates.Focused) == TreeNodeStates.Focused;
if (isFocused)
ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds, foreColor, backColor);
Your code has more problems:
Issue 1: Images
If you use icons for nodes use the TreeView.ImageList and TreeNode.ImageKey properties. Otherwise, no space will be allocated for the image at drawing. In this case you can use TreeViewDrawMode.OwnerDrawText DrawMode.
Issue 2: Fonts
Do not use other font than the font of the node or the tree because it can have unexpected size. If this SegoeUI font is a default one use it at your TreeView instance instead. Then you can obtain the required font like this:
Font font = e.Node.NodeFont ?? e.Node.TreeView.Font;
Issue 3: Texts
You use Graphics.DrawString for drawing texts, which uses GDI+. However, starting with .NET 2.0 the default text rendering method is by GDI, unless CompatibleTextRendering property of a control is true. They produce slightly different text sizes.
To use GDI, for which the size of a label is calculated use the TextRenderer class instead:
TextRenderer.DrawText(e.Graphics, e.Node.Text, font, e.Bounds, foreColor, TextFormatFlags.GlyphOverhangPadding | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis | TextFormatFlags.NoPrefix);

Related

Display header text after CellPainting event in winforms datagridview

I have a datagridview control where I want to put a 3px line at the bottom of each header cell to look something like
I have put code in CellPainting even for the datagridview like:
if (e.RowIndex < 0) // headers
{
Rectangle newRect = new Rectangle(e.CellBounds.X, e.CellBounds.Y - 1 + e.CellBounds.Height, e.CellBounds.Width, 2);
using (Brush gridBrush = new SolidBrush(Color.Red))
{
e.Graphics.FillRectangle(gridBrush, newRect);
}e.Handled = true;
}
The red line appears correctly (I will add the 3px later). However, the header text now is missing.
I am assuming that setting the e.Handled = true; tells to not continue to draw the original header text. If I set it to false, then the red line disappears. There is no base.CellPainting type concept for this control (apparently).
I know I can draw the text myself, but then I have to worry about alignment, font...
Is there now way to tell the system to do both the line AND draw original header text?
I am willing to try other approaches if necessary.
There is no base.CellPainting type concept for this control
Indeed; the DGV has more options than just calling the base event.
Instead you can let it draw the parts separately and in the order you want:
if (e.RowIndex < 0) // headers
{
e.PaintBackground(e.CellBounds, true); // draw the default background
Rectangle newRect =
new Rectangle(e.CellBounds.X, e.CellBounds.Bottom - 2, e.CellBounds.Width, 2);
e.Graphics.FillRectangle(Brushes.Red, newRect); // now draw the red line
e.PaintContent(e.CellBounds); // finally draw the text in the default way
e.Handled = true; // done
}
If you disable dgv.EnableHeadersVisualStyles you can also set many other properties to be used when drawing the column headers..
For even finer-tuned options you may want to look into MSDN.

TreeView Owner Draw Anomaly

I am using Microsoft Visual Studio Community 2017 version 15.7.2, and .NET Framework version 4.7.03056.
I am using the Winforms TreeView and am modifying its default behavior to make it a little bit more like the Windows Explorer tree view. I set the following properties:
LineHeight` 22
DrawMode OwnerDrawAll
I am using the following for the DrawNode event. This code uses right and down bracket bitmaps (which are 16x16) to show expanded or unexpanded nodes, and uses custom colors for select/focus highlighting. Nothing exotic.
private void treeDir_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
const int indent = 12;
const int markerSpacing = 20;
int leftPos = e.Bounds.Left + e.Node.Level * indent;
Brush selectBrush;
Pen pen;
Graphics g = e.Graphics;
e.DrawDefault = false;
if (e.Node.IsSelected)
{
if (e.Node.TreeView.Focused)
{
selectBrush = new SolidBrush(FocusedBackgroundColor);
pen = new Pen(new SolidBrush(FocusedPenColor));
}
else
{
selectBrush = new SolidBrush(UnfocusedBackgroundColor);
pen = new Pen(new SolidBrush(UnfocusedPenColor));
}
g.FillRectangle(selectBrush, e.Bounds);
g.DrawRectangle(pen, e.Bounds);
}
if (e.Node.Nodes.Count > 0)
{
if (e.Node.IsExpanded)
{
g.DrawImage(Properties.Resources.Expanded, leftPos+2, e.Bounds.Top+2);
}
else
{
g.DrawImage(Properties.Resources.Unexpanded, leftPos+2, e.Bounds.Top+2);
}
}
g.DrawString(
e.Node.Text, CommonFont, new SolidBrush(Color.Black), leftPos + markerSpacing, e.Bounds.Top+2);
}
What's happening is that when the form is first shown, if I expand a node that is not the first node, it also overwrites (transparently overlays) the first node text. Here's the sequence.
On start up of the form:
Then I double click Node 4:
If I double click the first node, the problem clears up:
From this point forward, if I double click Node 4, the problem no longer occurs. Double clicking the first node clears up the problem and avoids it for the life of the form after that point for Node 4. However, if I double click another expandable node further down, it happens again.
Is this a bug in TreeView or am I doing something incorrect in my owner draw?
The DrawNode event is called rather too often when doubleclicking and one set of calls has a bounds rectangle that is Empty.
(Maybe the reasoning was: If all drawing is happening only in the empty rectangle nothing will show. Hm..)
So as a workaround you can shortcut the DrawNode event for all the wrong calls at the beginning of the event:
if (e.Bounds.Height < 1 || e.Bounds.Width < 1) return;
I also recommend text rendering like this..:
TextRenderer.DrawText(g, e.Node.Text, CommonFont,
new Point( leftPos + markerSpacing, e.Bounds.Top+2), Color.Black);
TextRenderer is always recommended over Graphics.DrawString for forms as it improves on several shortcomings.

Recoloring TabControl

I have a tab control which I want to customize. To be more specific, I want to change the color of the tab page header, as well as the color of that white line around the tab page (check first picture).
I thought of using a custom renderer to do this (similar to recoloring a menu strip, for example), but I'm not sure how to do this. I've also read that setting the DrawMode to OwnerDrawFixed may do this, but using this option makes the the tab control look as if my program was made in the '90s (check second picture).
What I really want to do is to keep the tabs simple and flat and change their color. Check the way tabs are in Visual Studio as an example (check third picture).
Any ideas?
Edit: Another picture of the tab page so that it's more clear what this "white line" is.
When you use OwnerDrawFixed it means you will supply the drawing code. If you did not hook up and use the DrawItem event, nothing gets drawn. This will look much the same as yours at design time, because the event is not firing. For design time painting, you'd have to subclass the control and use OnDrawItem.
// colors to use
private Color[] TColors = {Color.Salmon, Color.White, Color.LightBlue};
private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
// get ref to this page
TabPage tp = ((TabControl)sender).TabPages[e.Index];
using (Brush br = new SolidBrush(TColors[e.Index]))
{
Rectangle rect = e.Bounds;
e.Graphics.FillRectangle(br, e.Bounds);
rect.Offset(1, 1);
TextRenderer.DrawText(e.Graphics, tp.Text,
tp.Font, rect, tp.ForeColor);
// draw the border
rect = e.Bounds;
rect.Offset(0, 1);
rect.Inflate(0, -1);
// ControlDark looks right for the border
using (Pen p = new Pen(SystemColors.ControlDark))
{
e.Graphics.DrawRectangle(p, rect);
}
if (e.State == DrawItemState.Selected) e.DrawFocusRectangle();
}
}
basic result:
The tab thumb looks a bit cramped to me and not as tall as the default. So, I added a TFontSize to draw the text at a different size than the Font.
Set the TabControl.Font to 10 (which seems to be plenty), so that Windows draws a slightly larger thumb/header. If you still draw the text at the default 8.25, there is more room:
private float TFontSize = 8.25F; // font drawing size
...
using (Font f = new Font(tp.Font.FontFamily,TFontSize))
{
// shift for a gutter/padding
rect.Offset(1, 1);
TextRenderer.DrawText(e.Graphics, tp.Text,
f, rect, tp.ForeColor);
}
One thing you will loose this way is the VisualStyles effect, but they would seem to clash with colored tabs anyway.

ListView with background image and custom cell colors

I have a listview that uses custom cell colors, but when I set a background image in the listview, the custom cell colors will not appear anymore. I tried to remove the background image temporarily (when assembling the list) and restore it after applying cell colors. This results in no custom colors but shows the background. I would like to combine these 2 listview properties if possible.
My code for setting/removing background image:
list.BackgroundImage = Properties.Resources.bgalpha;
list.BackgroundImage = null;
A part of my code for setting custom cell colors:
for (int i = 0; i < kavels.Count(); i++ )
{
if (list.Items[i].SubItems[1].Text != "0")
{
list.Items[i].UseItemStyleForSubItems = false;
list.Items[i].SubItems[1].BackColor = Color.LightGreen;
}
}
Here are two screenshots:
List view with background: http://i.imgur.com/aHUXAVh.png
List view without background: http://i.imgur.com/sO83wTP.png
I also tried making a PictureBox with a transparent background along with a png image with transparency on top of the ListView, but that also didn't work obviously.
You have two options:
You could overlay a Panel or a PictureBox with a semi-transprent Image. For this to work you would have to make it sit inside the ListView, so that it is the Parent of the overlay.
But that will make the Listview non-clickable. - Another problem with this is that it will slightly color the text, so it won't look quite right.
Or you can set the ListView to OwnerDraw = true and add code to do the drawing yourself.
Here is an example, non-scrolled and scrolled:
Note that the original BackgroundImage shines through the emtpy space to the right.
If you owner-draw a ListView in Details mode you need to code events to draw subitems and headers; note the class level variable to hold the itemHeight; this assumes they all have the same Height .. The other one is need for horizontal scrolling.
int itemHeight = 0; // we need this number!
int itemLeft = 0; // we need this number, too
private void listView1_DrawColumnHeader(object sender,
DrawListViewColumnHeaderEventArgs e)
{
Rectangle R0 = listView1.GetItemRect(0);
itemHeight = R0.Height; // we need this number!
itemLeft = R0.Left; // we need this number too
e.DrawBackground();
e.DrawText();
}
private void listView1_DrawSubItem(object sender,
DrawListViewSubItemEventArgs e)
{
Rectangle rrr = listView1.GetItemRect(e.ItemIndex);
Rectangle rect = e.Bounds;
Rectangle rect0 = new Rectangle(rect.X - itemLeft , itemHeight * e.ItemIndex,
rect.Width, rect.Height);
Image img = listView1.BackgroundImage;
e.Graphics.DrawImage(img, rect, rect0, GraphicsUnit.Pixel);
using (SolidBrush brush = new SolidBrush(e.SubItem.BackColor))
e.Graphics.FillRectangle(brush, rect);
e.DrawText();
}
Here is the code to set the colors in the ListViewItem lvi for the example:
lvi.UseItemStyleForSubItems = false;
lvi.BackColor = Color.FromArgb(66, Color.LightBlue);
lvi.SubItems[1].BackColor = Color.FromArgb(77, Color.LightGreen);
lvi.SubItems[2].BackColor = Color.FromArgb(88, Color.LightPink);
Note that the code assumes your background is one large image and no tiling is involved! Also the code works only if you don't have groups!
ObjectListView -- an open source wrapper around a standard .NET ListView -- provides ImageOverlays and true background images too. They both work with colour cells.

WinForms Gradient (Image or By Code) - OnDraw Event

Ok so im starting to get stuck into my design and get the style right.
My Theme is using a kryptonForm style GUI but kyryptonForms do not not have a pre designed ListView, so im having to build this myself
My Application is a messenger system based on XMPP/Jabber so you can guess how i would like my contact list to be designed.
i have done most of the positioning but im struggling on styling each contact row.
Im aiming for some transparent overlay simmerler to the MSN Live messenger Contact List
Heres my OnDraw Event code atm and im struggling to figure out the best way to do the gradient
private void ContactItem_OnPaintDraw(object sender, DrawListViewItemEventArgs e)
{
Rectangle ImageRect = e.Bounds;
ImageRect.Inflate(-2, -2);
ImageRect.Width = 32;
Rectangle TextRect = e.Bounds;
TextRect.X = ImageRect.Right + 2;
TextRect.Width = e.Bounds.Width - TextRect.X;
Rectangle IconRect = TextRect;
IconRect.Inflate(-1, 0);
IconRect.Y = ImageRect.Bottom - 16;
IconRect.Width = 16;
IconRect.Height = 16;
if ((e.State & ListViewItemStates.Selected) != 0)
{
// Draw the background and focus rectangle for a selected item.
e.Graphics.FillRectangle(ContactListBackgroundBrush, e.Bounds);
e.DrawFocusRectangle();
}
else
{
// Draw the background for an unselected item.
e.Graphics.FillRectangle(Brushes.White, e.Bounds);
}
if (ListViewContacts.View != View.Details)
{
e.Graphics.DrawImage((Image)Resources.UserIconDefault, ImageRect);
TextRenderer.DrawText(e.Graphics, e.Item.Text, e.Item.Font, TextRect, e.Item.ForeColor, TextFormatFlags.GlyphOverhangPadding);
}
}
And the ContactListBackgroundBrush var is like so
private Brush ContactListBackgroundBrush = new SolidBrush(Color.FromArgb(33, 162, 191));
its this that i need to convert to the styled element
alt text http://screensnapr.com/u/yeq8o0.png
Im Looking to get this Highlighted style without importing any specific windows 7 DLL files as the App is used for windows XP as well.
Hope you guys can help me :)
You can define a brush as a LinearGradientBrush, look for the msnd documentation. This is IMHO the best way, to draw gradiants..

Categories

Resources