I want to customize DrawNode in OwnerDrawText mode in a TreeView. I found it very slow even with this handler:
void RegistryTreeDrawNode(object sender, DrawTreeNodeEventArgs e)
{
e.DrawDefault = true;
}
Am I doing something wrong?
Thanks.
I think you may need to show a bit more code for what you're trying to do. There shouldn't be anything noticeably different drawing like that, versus not owner drawing at all; you're basically overriding the default draw and then undoing it in what you posted. It's ugly for no gain... but shouldn't be a perf hit.
So switching away from the lack of code and going after your core desire of a custom drawn tree, let me tell you that there is NOT a lot of good information out in the wild right now.
I've been doing my own custom treeview work over the past few days and will probably end up writing a tutorial on all that I've learned. In the meantime feel free to take a look at my code and see if it helps you out.
Mine was simply a custom drawn explorer treeview. The code that populates the treeview is separate from the TreeView drawing code. You'll probably need to add your own +/- images if you wanted to run my code.
Utilities\IconReader.cs
using System;
using System.Runtime.InteropServices;
namespace TreeViewTestProject.Utilities
{
public class IconReader
{
public enum IconSize
{
Large = 0,
Small = 1
};
public enum FolderType
{
Open = 0,
Closed = 1
};
/// <summary>
/// Returns an icon for a given file - indicated by the name parameter.
/// </summary>
/// <param name="name">Pathname for file.</param>
/// <param name="size">Large or small</param>
/// <param name="linkOverlay">Whether to include the link icon</param>
/// <returns>System.Drawing.Icon</returns>
public static System.Drawing.Icon GetFileIcon(string name, IconSize size, bool linkOverlay)
{
Shell32.SHFILEINFO shfi = new Shell32.SHFILEINFO();
uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES;
if(true == linkOverlay) flags |= Shell32.SHGFI_LINKOVERLAY;
/* Check the size specified for return. */
if(IconSize.Small == size)
{
flags |= Shell32.SHGFI_SMALLICON;
}
else
{
flags |= Shell32.SHGFI_LARGEICON;
}
Shell32.SHGetFileInfo(name,
Shell32.FILE_ATTRIBUTE_NORMAL,
ref shfi,
(uint)System.Runtime.InteropServices.Marshal.SizeOf(shfi),
flags);
// Copy (clone) the returned icon to a new object, thus allowing us to clean-up properly
System.Drawing.Icon icon = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shfi.hIcon).Clone();
User32.DestroyIcon(shfi.hIcon); // Cleanup
return icon;
}
/// <summary>
/// Used to access system folder icons.
/// </summary>
/// <param name="size">Specify large or small icons.</param>
/// <param name="folderType">Specify open or closed FolderType.</param>
/// <returns>System.Drawing.Icon</returns>
public static System.Drawing.Icon GetFolderIcon(string Foldername, IconSize size, FolderType folderType)
{
// Need to add size check, although errors generated at present!
uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES;
if(FolderType.Open == folderType)
{
flags |= Shell32.SHGFI_OPENICON;
}
if(IconSize.Small == size)
{
flags |= Shell32.SHGFI_SMALLICON;
}
else
{
flags |= Shell32.SHGFI_LARGEICON;
}
// Get the folder icon
Shell32.SHFILEINFO shfi = new Shell32.SHFILEINFO();
Shell32.SHGetFileInfo(Foldername,
Shell32.FILE_ATTRIBUTE_DIRECTORY,
ref shfi,
(uint)System.Runtime.InteropServices.Marshal.SizeOf(shfi),
flags);
System.Drawing.Icon.FromHandle(shfi.hIcon); // Load the icon from an HICON handle
// Now clone the icon, so that it can be successfully stored in an ImageList
System.Drawing.Icon icon = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shfi.hIcon).Clone();
User32.DestroyIcon(shfi.hIcon); // Cleanup
return icon;
}
}
public class Shell32
{
public const int MAX_PATH = 256;
[StructLayout(LayoutKind.Sequential)]
public struct SHITEMID
{
public ushort cb;
[MarshalAs(UnmanagedType.LPArray)]
public byte[] abID;
}
[StructLayout(LayoutKind.Sequential)]
public struct ITEMIDLIST
{
public SHITEMID mkid;
}
[StructLayout(LayoutKind.Sequential)]
public struct BROWSEINFO
{
public IntPtr hwndOwner;
public IntPtr pidlRoot;
public IntPtr pszDisplayName;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszTitle;
public uint ulFlags;
public IntPtr lpfn;
public int lParam;
public IntPtr iImage;
}
// Browsing for directory.
public const uint BIF_RETURNONLYFSDIRS = 0x0001;
public const uint BIF_DONTGOBELOWDOMAIN = 0x0002;
public const uint BIF_STATUSTEXT = 0x0004;
public const uint BIF_RETURNFSANCESTORS = 0x0008;
public const uint BIF_EDITBOX = 0x0010;
public const uint BIF_VALIDATE = 0x0020;
public const uint BIF_NEWDIALOGSTYLE = 0x0040;
public const uint BIF_USENEWUI = (BIF_NEWDIALOGSTYLE | BIF_EDITBOX);
public const uint BIF_BROWSEINCLUDEURLS = 0x0080;
public const uint BIF_BROWSEFORCOMPUTER = 0x1000;
public const uint BIF_BROWSEFORPRINTER = 0x2000;
public const uint BIF_BROWSEINCLUDEFILES = 0x4000;
public const uint BIF_SHAREABLE = 0x8000;
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public const int NAMESIZE = 80;
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NAMESIZE)]
public string szTypeName;
};
public const uint SHGFI_ICON = 0x000000100; // get icon
public const uint SHGFI_DISPLAYNAME = 0x000000200; // get display name
public const uint SHGFI_TYPENAME = 0x000000400; // get type name
public const uint SHGFI_ATTRIBUTES = 0x000000800; // get attributes
public const uint SHGFI_ICONLOCATION = 0x000001000; // get icon location
public const uint SHGFI_EXETYPE = 0x000002000; // return exe type
public const uint SHGFI_SYSICONINDEX = 0x000004000; // get system icon index
public const uint SHGFI_LINKOVERLAY = 0x000008000; // put a link overlay on icon
public const uint SHGFI_SELECTED = 0x000010000; // show icon in selected state
public const uint SHGFI_ATTR_SPECIFIED = 0x000020000; // get only specified attributes
public const uint SHGFI_LARGEICON = 0x000000000; // get large icon
public const uint SHGFI_SMALLICON = 0x000000001; // get small icon
public const uint SHGFI_OPENICON = 0x000000002; // get open icon
public const uint SHGFI_SHELLICONSIZE = 0x000000004; // get shell size icon
public const uint SHGFI_PIDL = 0x000000008; // pszPath is a pidl
public const uint SHGFI_USEFILEATTRIBUTES = 0x000000010; // use passed dwFileAttribute
public const uint SHGFI_ADDOVERLAYS = 0x000000020; // apply the appropriate overlays
public const uint SHGFI_OVERLAYINDEX = 0x000000040; // Get the index of the overlay
public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
public const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
[DllImport("Shell32.dll")]
public static extern IntPtr SHGetFileInfo(
string pszPath,
uint dwFileAttributes,
ref SHFILEINFO psfi,
uint cbFileInfo,
uint uFlags
);
}
public class User32
{
/// <summary>
/// Provides access to function required to delete handle. This method is used internally
/// and is not required to be called separately.
/// </summary>
/// <param name="hIcon">Pointer to icon handle.</param>
/// <returns>N/A</returns>
[DllImport("User32.dll")]
public static extern int DestroyIcon(IntPtr hIcon);
}
}
ExplorerTreeView.cs :
using System;
using System.IO;
using System.Windows.Forms;
using TreeViewTestProject.Utilities;
using System.Collections;
namespace TreeViewTestProject
{
public partial class ExplorerTreeView : TreeViewEx
{
#region ExplorerNodeSorter Class
private class ExplorerNodeSorter : IComparer
{
public int Compare(object x, object y)
{
TreeNode nx = x as TreeNode;
TreeNode ny = y as TreeNode;
bool nxDir = (nx.ImageKey == kDirectoryImageKey);
bool nyDir = (ny.ImageKey == kDirectoryImageKey);
if(nxDir && !nyDir)
{
return -1;
}
else if(nyDir && !nxDir)
{
return 1;
}
else
{
return string.Compare(nx.Text, ny.Text);
}
}
}
#endregion
private const string kDirectoryImageKey = "directory";
private const string kReplacementText = "C43C65D1-D40F-46F0-BC5E-57265322DDFC";
public ExplorerTreeView()
{
InitializeComponent();
this.BeforeExpand += new TreeViewCancelEventHandler(ExplorerTreeView_BeforeExpand);
this.ImageList = m_FileIcons;
this.TreeViewNodeSorter = new ExplorerNodeSorter();
this.LabelEdit = true;
// Create the root of the tree and populate it
PopulateTreeView(#"C:\");
}
private void PopulateTreeView(string DirectoryName)
{
this.BeginUpdate();
string rootDir = DirectoryName;
TreeNode rootNode = CreateTreeNode(rootDir);
rootNode.Text = rootDir;
this.Nodes.Add(rootNode);
PopulateDirectory(rootNode);
this.EndUpdate();
}
private bool PathIsDirectory(string FullPath)
{
FileAttributes attr = File.GetAttributes(FullPath);
return ((attr & FileAttributes.Directory) == FileAttributes.Directory);
}
private TreeNode CreateTreeNode(string FullPath)
{
string key = FullPath.ToLower();
string name = "";
object tag = null;
if(PathIsDirectory(key))
{
DirectoryInfo info = new DirectoryInfo(FullPath);
key = kDirectoryImageKey;
name = info.Name;
tag = info;
}
else
{
FileInfo info = new FileInfo(FullPath);
name = info.Name;
tag = info;
}
if(!m_FileIcons.Images.ContainsKey(key))
{
if(key == "directory")
{
m_FileIcons.Images.Add(key, IconReader.GetFolderIcon(Environment.CurrentDirectory, IconReader.IconSize.Small, IconReader.FolderType.Open).ToBitmap());
}
else
{
m_FileIcons.Images.Add(key, IconReader.GetFileIcon(FullPath, IconReader.IconSize.Small, false));
}
}
TreeNode node = new TreeNode(name);
node.Tag = tag;
node.ImageKey = key;
node.SelectedImageKey = key;
return node;
}
private void PopulateDirectory(TreeNode ParentNode)
{
DirectoryInfo parentInfo = ParentNode.Tag as DirectoryInfo;
foreach(DirectoryInfo subDir in parentInfo.GetDirectories())
{
TreeNode child = CreateTreeNode(subDir.FullName);
PopulateForExpansion(child);
ParentNode.Nodes.Add(child);
}
foreach(FileInfo file in parentInfo.GetFiles())
{
ParentNode.Nodes.Add(CreateTreeNode(file.FullName));
}
}
private void PopulateForExpansion(TreeNode ParentNode)
{
// We need the +/- to show up if this directory isn't empty... but only want to populate the node on demand
DirectoryInfo parentInfo = ParentNode.Tag as DirectoryInfo;
try
{
if((parentInfo.GetDirectories().Length > 0) || (parentInfo.GetFiles().Length > 0))
{
ParentNode.Nodes.Add(kReplacementText);
}
}
catch { }
}
private void ReplacePlaceholderDirectoryNode(TreeNode ParentNode)
{
if((ParentNode.Nodes.Count == 1) && (ParentNode.Nodes[0].Text == kReplacementText))
{
ParentNode.Nodes.Clear();
PopulateDirectory(ParentNode);
}
}
private void ExplorerTreeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
this.BeginUpdate();
ReplacePlaceholderDirectoryNode(e.Node);
this.EndUpdate();
}
}
}
TreeViewEx.cs:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace TreeViewTestProject
{
public partial class TreeViewEx : TreeView
{
// Notes: TextRenderer uses GDI to render the text, whereas Graphics uses GDI+. "TreeView" has existed for a long long time
// and thus uses GDI under the covers. For User Drawing TreeNode's, we need to make sure we use the TextRenderer version
// of text rendering functions.
#region Properties
private DashStyle m_SelectionDashStyle = DashStyle.Dot;
public DashStyle SelectionDashStyle
{
get { return m_SelectionDashStyle; }
set { m_SelectionDashStyle = value; }
}
private DashStyle m_LineStyle = DashStyle.Solid;
public DashStyle LineStyle
{
get { return m_LineStyle; }
set { m_LineStyle = value; }
}
private bool m_ShowLines = true;
public new bool ShowLines // marked as 'new' to replace base functionality fixing ShowLines/FullRowSelect issues in base.
{
get { return m_ShowLines; }
set { m_ShowLines = value; }
}
protected override CreateParams CreateParams
{
get
{
// Removes all the flickering of repainting node's
// This is the only thing I found that works properly for doublebuffering a treeview.
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // WS_CLIPCHILDREN
return cp;
}
}
#endregion
[DllImport("user32.dll", ExactSpelling = false, CharSet = CharSet.Auto)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, HandleRef lParam);
private const int GWL_STYLE = -16;
private const int WS_VSCROLL = 0x00200000;
private const uint TV_FIRST = 0x1100;
private const uint TVM_EDITLABELA = (TV_FIRST + 14);
private const uint TVM_EDITLABELW = (TV_FIRST + 65);
private bool m_SelectionChanged = false;
private bool m_DoubleClicked = false;
private bool m_HierarchyChanged = false;
public TreeViewEx()
{
InitializeComponent();
// ShowLines must be "false" for FullRowSelect to work - so we're overriding the variable to correct for that.
base.ShowLines = false;
this.FullRowSelect = true;
this.ItemHeight = 21;
this.DrawMode = TreeViewDrawMode.OwnerDrawAll;
this.DrawNode += OnDrawNode;
}
private void OnDrawNode(object sender, DrawTreeNodeEventArgs e)
{
e.DrawDefault = false;
if(e.Node.Bounds.IsEmpty) return;
// Clear out the previous contents for the node. If we don't do this, when you mousehover the font will get slightly more bold
Rectangle bounds = new Rectangle(0, e.Node.Bounds.Y, this.Width - 1, e.Node.Bounds.Height - 1);
e.Graphics.FillRectangle(SystemBrushes.Window, bounds);
// Draw everything
DrawNodeFocusedHighlight(e);
DrawNodeLines(e);
DrawPlusMinus(e);
DrawNodeIcon(e);
DrawNodeText(e);
}
private void DrawNodeFocusedHighlight(DrawTreeNodeEventArgs e)
{
if(SelectedNode != e.Node) return;
int scrollWidth = 0;
if(VScrollVisible())
{
scrollWidth = SystemInformation.VerticalScrollBarWidth;
}
Rectangle bounds = new Rectangle(0, e.Node.Bounds.Y, this.Width - scrollWidth, e.Node.Bounds.Height - 1);
if(!e.Node.IsEditing)
{
e.Graphics.FillRectangle(SystemBrushes.Highlight, bounds);
}
using(Pen focusPen = new Pen(Color.Black))
{
focusPen.DashStyle = SelectionDashStyle;
bounds = new Rectangle(0, e.Node.Bounds.Y, this.Width - scrollWidth - 5, e.Node.Bounds.Height - 2);
e.Graphics.DrawRectangle(focusPen, bounds);
}
}
private void DrawNodeText(DrawTreeNodeEventArgs e)
{
if(e.Node.Bounds.IsEmpty) return;
if(e.Node.IsEditing) return;
Rectangle bounds = e.Node.Bounds;
using(Font font = e.Node.NodeFont)
{
bounds.Width = TextRenderer.MeasureText(e.Node.Text, font).Width;
bounds.Y -= 1;
bounds.X += 1;
if(IsRootNode(e.Node))
{
bounds = new Rectangle(this.Margin.Size.Width + Properties.Resources.minus.Width + 9, 0, bounds.Width, bounds.Height);
}
Color fontColor = SystemColors.InactiveCaptionText;
if(this.Focused)
{
fontColor = e.Node.IsSelected?SystemColors.HighlightText:this.ForeColor;
}
TextRenderer.DrawText(e.Graphics, e.Node.Text, font, bounds, fontColor);
}
}
private bool IsRootNode(TreeNode Node)
{
return (Node.Level == 0 && Node.PrevNode == null);
}
private void DrawNodeLines(DrawTreeNodeEventArgs e)
{
DrawNodeLineVertical(e);
DrawNodeLineHorizontal(e);
}
private void DrawNodeLineVertical(DrawTreeNodeEventArgs e)
{
if(IsRootNode(e.Node)) return;
if(!ShowLines) return;
Pen linePen = new Pen(Color.Black);
linePen.DashStyle = LineStyle;
for(int x = 0; x < e.Node.Level; x++)
{
int xLoc = this.Indent + (x * this.Indent) + (Properties.Resources.minus.Width / 2);
int height = e.Bounds.Height;
if(ShouldDrawVerticalLineForLevel(e.Node, x))
{
e.Graphics.DrawLine(linePen, xLoc, e.Bounds.Top, xLoc, e.Bounds.Top + height);
}
}
// Draw the half line for the last node
if(e.Node.Parent.LastNode == e.Node)
{
int halfLoc = (e.Node.Level * this.Indent) + (Properties.Resources.minus.Width / 2);
e.Graphics.DrawLine(linePen, halfLoc, e.Bounds.Top, halfLoc, (e.Bounds.Top + e.Bounds.Height / 2) - 1);
}
}
private bool ShouldDrawVerticalLineForLevel(TreeNode Current, int Level)
{
TreeNode node = Current;
TreeNode c = Current;
while(node.Level != Level)
{
c = node;
node = node.Parent;
}
return !(node.LastNode == c);
}
private void DrawNodeLineHorizontal(DrawTreeNodeEventArgs e)
{
if(IsRootNode(e.Node)) return;
if(!ShowLines) return;
Pen linePen = new Pen(Color.Black);
int xLoc = (e.Node.Level * this.Indent) + (Properties.Resources.minus.Width / 2);
int midY = (e.Bounds.Top + e.Bounds.Bottom) / 2 - 1;
e.Graphics.DrawLine(linePen, xLoc, midY, xLoc + 7, midY);
}
private void DrawNodeIcon(DrawTreeNodeEventArgs e)
{
if(this.ImageList == null) return;
int indent = (e.Node.Level * this.Indent) + this.Margin.Size.Width;
int iconLeft = indent + this.Indent;
int imgIndex = this.ImageList.Images.IndexOfKey(e.Node.ImageKey);
if(!IsRootNode(e.Node))
{
if(imgIndex >= 0)
{
Image img = this.ImageList.Images[imgIndex];
int y = (e.Bounds.Y + e.Bounds.Height / 2) - (img.Height / 2) - 1;
e.Graphics.DrawImage(img, new Rectangle(iconLeft, y, img.Width, img.Height), new Rectangle(0, 0, img.Width, img.Height), GraphicsUnit.Pixel);
}
}
}
private void DrawPlusMinus(DrawTreeNodeEventArgs e)
{
if(e.Node.Nodes.Count == 0) return;
int indent = (e.Node.Level * this.Indent) + this.Margin.Size.Width;
int iconLeft = indent + this.Indent;
Image img = Properties.Resources.plus;
if(e.Node.IsExpanded) img = Properties.Resources.minus;
e.Graphics.DrawImage(img, iconLeft - img.Width - 2, (e.Bounds.Y + e.Bounds.Height / 2) - (img.Height / 2) - 1);
}
private bool VScrollVisible()
{
int style = GetWindowLong(this.Handle, GWL_STYLE);
return ((style & WS_VSCROLL) != 0);
}
private void BeginEditNode()
{
if(this.SelectedNode == null) return;
if(!this.LabelEdit) throw new Exception("This TreeView is not configured with LabelEdit=true");
IntPtr result = SendMessage(new HandleRef(this, this.Handle), TVM_EDITLABELA, IntPtr.Zero, new HandleRef(this.SelectedNode, this.SelectedNode.Handle));
if(result == IntPtr.Zero)
{
throw new Exception("Failed to send EDITLABEL message to TreeView control.");
}
}
private void TreeViewEx_BeforeLabelEdit(object sender, NodeLabelEditEventArgs e)
{
if(m_DoubleClicked)
{
m_DoubleClicked = false;
return;
}
if(m_SelectionChanged)
{
e.CancelEdit = true;
m_SelectionChanged = false;
}
}
private void TreeViewEx_MouseDoubleClick(object sender, MouseEventArgs e)
{
if(m_HierarchyChanged)
{
m_HierarchyChanged = false;
return;
}
if((e.Button & MouseButtons.Left) > 0)
{
if(this.LabelEdit && (this.SelectedNode != null))
{
m_DoubleClicked = true;
BeginInvoke(new MethodInvoker(delegate() { this.SelectedNode.BeginEdit(); }));
}
}
}
private void TreeViewEx_AfterCollapse(object sender, TreeViewEventArgs e)
{
m_HierarchyChanged = true;
}
private void TreeViewEx_AfterExpand(object sender, TreeViewEventArgs e)
{
m_HierarchyChanged = true;
}
}
}
Related
I used:
myComboBox.DrawMode = DrawMode.OwnerDrawFixed;
Then I implemented the DrawItem event like so:
private void myComboBox_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index < 0)
return;
string text = myComboBox.GetItemText(myComboBox.Items[e.Index]);
e.DrawBackground();
using var brush = new SolidBrush(e.ForeColor);
e.Graphics.DrawString(text, e.Font!, brush, e.Bounds);
if (myComboBox.DroppedDown && (e.State & DrawItemState.Selected) == DrawItemState.Selected)
{
var item = (IndexedItem)myComboBox.Items[e.Index]; // IndexedItem is my own class to hold the index and name of a combobox item
if (!string.IsNullOrWhiteSpace(item.Name))
myToolTip.Show(item.Name, myComboBox, e.Bounds.Right, e.Bounds.Bottom);
}
e.DrawFocusRectangle();
}
The purpose is to show a tooltip for each item when I hover it. The above code works fine but sometimes the dropdown is shown above instead of below due to available space.
The problem now is, that DrawBackground etc use the adjusted positions but e.Bounds still contains the area below the control. So the dropdown is shown above but the tooltip below the combobox. I guess DrawBackground modifies the bounds internally but doesn't write the adjusted value back to e.Bounds. Now the question is, how can I determine the correct bounds of the item in that case?
Ok I managed to find the correct bounds via interops like this:
Rectangle GetItemBounds(int index)
{
const int SB_VERT = 0x1;
const int SIF_RANGE = 0x1;
const int SIF_POS = 0x4;
const uint GETCOMBOBOXINFO = 0x0164;
var info = new COMBOBOXINFO();
info.cbSize = Marshal.SizeOf(info);
SendMessageW(
myComboBox.Handle,
GETCOMBOBOXINFO,
IntPtr.Zero,
ref info);
GetWindowRect(info.hwndList, out RECT rc);
var dropdownArea = new Rectangle(myComboBox.PointToClient(rc.Location), rc.Size);
int yDiff = index * myComboBox.ItemHeight;
var scrollInfo = new SCROLLINFO();
scrollInfo.cbSize = (uint)Marshal.SizeOf(scrollInfo);
scrollInfo.fMask = SIF_RANGE | SIF_POS;
GetScrollInfo(info.hwndList, SB_VERT, ref scrollInfo);
int scrollY = scrollInfo.nPos * myComboBox.ItemHeight;
return new Rectangle(dropdownArea.X, dropdownArea.Y - scrollY + yDiff, rc.Width, myComboBox.ItemHeight);
}
And then use it like this:
var realBounds = GetItemBounds(e.Index);
I am not sure if this is the best approach but it even works with scrolling inside the dropdown now.
The interops look like this:
[StructLayout(LayoutKind.Sequential)]
struct COMBOBOXINFO
{
public Int32 cbSize;
public RECT rcItem, rcButton;
public int buttonState;
public IntPtr hwndCombo, hwndEdit, hwndList;
}
[Serializable, StructLayout(LayoutKind.Sequential)]
struct SCROLLINFO
{
public uint cbSize;
public uint fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
}
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetScrollInfo(IntPtr hwnd, int fnBar, ref SCROLLINFO lpsi);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left, Top, Right, Bottom;
public RECT(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { }
public int X
{
get { return Left; }
set { Right -= (Left - value); Left = value; }
}
public int Y
{
get { return Top; }
set { Bottom -= (Top - value); Top = value; }
}
public int Height
{
get { return Bottom - Top; }
set { Bottom = value + Top; }
}
public int Width
{
get { return Right - Left; }
set { Right = value + Left; }
}
public System.Drawing.Point Location
{
get { return new System.Drawing.Point(Left, Top); }
set { X = value.X; Y = value.Y; }
}
public System.Drawing.Size Size
{
get { return new System.Drawing.Size(Width, Height); }
set { Width = value.Width; Height = value.Height; }
}
public static implicit operator System.Drawing.Rectangle(RECT r)
{
return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height);
}
public static implicit operator RECT(System.Drawing.Rectangle r)
{
return new RECT(r);
}
public static bool operator ==(RECT r1, RECT r2)
{
return r1.Equals(r2);
}
public static bool operator !=(RECT r1, RECT r2)
{
return !r1.Equals(r2);
}
public bool Equals(RECT r)
{
return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom;
}
public override bool Equals(object? obj)
{
if (obj is RECT)
return Equals((RECT)obj);
else if (obj is System.Drawing.Rectangle)
return Equals(new RECT((System.Drawing.Rectangle)obj));
return false;
}
public override int GetHashCode()
{
return ((System.Drawing.Rectangle)this).GetHashCode();
}
public override string ToString()
{
return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom);
}
}
[DllImport("user32")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT rc);
[DllImport("user32", ExactSpelling = true)]
public static extern IntPtr SendMessageW(
IntPtr hWnd,
uint Msg,
IntPtr wParam = default,
IntPtr lParam = default);
public unsafe static IntPtr SendMessageW<T>(
IntPtr hWnd,
uint Msg,
IntPtr wParam,
ref T lParam) where T : unmanaged
{
fixed (void* l = &lParam)
{
return SendMessageW(hWnd, Msg, wParam, (IntPtr)l);
}
}
I created an extension:
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static class ComboBoxExtensions
{
public static Rectangle GetItemBounds(this ComboBox comboBox, int index)
{
const int SB_VERT = 0x1;
const int SIF_RANGE = 0x1;
const int SIF_POS = 0x4;
const uint GETCOMBOBOXINFO = 0x0164;
var info = new Interop.COMBOBOXINFO();
info.cbSize = Marshal.SizeOf(info);
Interop.SendMessageW(
comboBox.Handle,
GETCOMBOBOXINFO,
IntPtr.Zero,
ref info);
Interop.GetWindowRect(info.hwndList, out Interop.RECT rc);
var dropdownArea = new Rectangle(comboBox.PointToClient(rc.Location), rc.Size);
int yDiff = index * comboBox.ItemHeight;
var scrollInfo = new Interop.SCROLLINFO();
scrollInfo.cbSize = (uint)Marshal.SizeOf(scrollInfo);
scrollInfo.fMask = SIF_RANGE | SIF_POS;
Interop.GetScrollInfo(info.hwndList, SB_VERT, ref scrollInfo);
int scrollY = scrollInfo.nPos * comboBox.ItemHeight;
return new Rectangle(dropdownArea.X, dropdownArea.Y - scrollY + yDiff, rc.Width, comboBox.ItemHeight);
}
}
I realize you have already answered your own question. This is just plan B.
Your post mentions that the purpose is to show a tooltip for each item when I hover it so this answer focuses on that aspect. It doesn't make use of the item bounds info but that would be available in the DrawItem handler if you still need it.
This approach is loosely based on this answer and takes advantage of comboBox.SelectedItem property changing (but without firing an event) as the mouse moves over the dropped-down list. It uses that change to draw tool tips on an owner-draw combo box (only it doesn't have to be an owner-draw combo box for it to work).
This sample code for MainForm starts a timer when the ComboBox drops and ends it when it closes. It polls the SelectedIndex property on each tick.
public MainForm()
{
InitializeComponent();
// Works with or without owner draw.
// This line may be commented out
comboBox.DrawMode = DrawMode.OwnerDrawFixed;
// Add items
foreach (var item in Enum.GetValues(typeof(ComboBoxItems)))
comboBox.Items.Add(item);
// Start timer on drop down
comboBox.DropDown += (sender, e) => _toolTipTimer.Enabled = true;
// End timer on closed
comboBox.DropDownClosed += (sender, e) =>
{
_toolTipTimer.Enabled = false;
_lastToolTipIndex = -1;
_toolTip.Hide(comboBox);
};
// Poll index while open
_toolTipTimer.Tick += (sender, e) =>
{
if(comboBox.SelectedIndex != _lastToolTipIndex)
{
_lastToolTipIndex = comboBox.SelectedIndex;
showToolTip();
}
};
If there is a new index, the local method showToolTip is invoked to show the correct text.
void showToolTip()
{
if (_lastToolTipIndex != -1)
{
// Get the item
var item = (ComboBoxItems)comboBox.Items[_lastToolTipIndex];
// Get the tip
var tt = TipAttribute.FromMember(item);
// Get the rel pos
var mousePosition = PointToClient(MousePosition);
var rel = new Point(
(mousePosition.X - comboBox.Location.X) - 10,
(mousePosition.Y - comboBox.Location.Y) - 30);
// Show the tip
_toolTip.Show(tt, comboBox, rel);
}
}
}
private ToolTip _toolTip = new ToolTip();
private readonly Timer _toolTipTimer = new Timer() { Interval = 100 };
private int _lastToolTipIndex = -1;
The list items are represented by this enum:
enum ComboBoxItems
{
[Tip("...a day keeps the doctor away")]
Apple,
[Tip("...you glad I didn't say 'banana?'")]
Orange,
[Tip("...job on the Tool Tips!")]
Grape,
}
Where TipAttribute is defined as:
internal class TipAttribute : Attribute
{
public TipAttribute(string tip) => Tip = tip;
public string Tip { get; }
public static string FromMember(Enum value) =>
((TipAttribute)value
.GetType()
.GetMember($"{value}")
.Single()
.GetCustomAttribute(typeof(TipAttribute))).Tip;
}
How could I change the slide button color?
not border color and not slide item colors.
I already change the slide item colors
Is there any way to change the color?
Flat ComboBox - Change border color and Dropdown button color
You need to handle WM_PAINT yourself and draw the border and the dropdown rectangle. This is the way that internal ComboBox.FlatComboAdapter class of .Net Framework works.
In this post, I've created a FlatComboBox, which draws the border and the dropdown in a flat style, having the following additional properties:
BorderColor: used for border and for the dropdown arrow
ButtonColor: used for dropdown area color.
Here is the code:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class FlatComboBox : ComboBox
{
private Color borderColor = Color.Gray;
[DefaultValue(typeof(Color), "Gray")]
public Color BorderColor
{
get { return borderColor; }
set
{
if (borderColor != value)
{
borderColor = value;
Invalidate();
}
}
}
private Color buttonColor = Color.LightGray;
[DefaultValue(typeof(Color), "LightGray")]
public Color ButtonColor
{
get { return buttonColor; }
set
{
if (buttonColor != value)
{
buttonColor = value;
Invalidate();
}
}
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_PAINT && DropDownStyle != ComboBoxStyle.Simple)
{
var clientRect = ClientRectangle;
var dropDownButtonWidth = SystemInformation.HorizontalScrollBarArrowWidth;
var outerBorder = new Rectangle(clientRect.Location,
new Size(clientRect.Width - 1, clientRect.Height - 1));
var innerBorder = new Rectangle(outerBorder.X + 1, outerBorder.Y + 1,
outerBorder.Width - dropDownButtonWidth - 2, outerBorder.Height - 2);
var innerInnerBorder = new Rectangle(innerBorder.X + 1, innerBorder.Y + 1,
innerBorder.Width - 2, innerBorder.Height - 2);
var dropDownRect = new Rectangle(innerBorder.Right + 1, innerBorder.Y,
dropDownButtonWidth, innerBorder.Height + 1);
if (RightToLeft == RightToLeft.Yes)
{
innerBorder.X = clientRect.Width - innerBorder.Right;
innerInnerBorder.X = clientRect.Width - innerInnerBorder.Right;
dropDownRect.X = clientRect.Width - dropDownRect.Right;
dropDownRect.Width += 1;
}
var innerBorderColor = Enabled ? BackColor : SystemColors.Control;
var outerBorderColor = Enabled ? BorderColor : SystemColors.ControlDark;
var buttonColor = Enabled ? ButtonColor : SystemColors.Control;
var middle = new Point(dropDownRect.Left + dropDownRect.Width / 2,
dropDownRect.Top + dropDownRect.Height / 2);
var arrow = new Point[]
{
new Point(middle.X - 3, middle.Y - 2),
new Point(middle.X + 4, middle.Y - 2),
new Point(middle.X, middle.Y + 2)
};
var ps = new PAINTSTRUCT();
bool shoulEndPaint = false;
IntPtr dc;
if (m.WParam == IntPtr.Zero)
{
dc = BeginPaint(Handle, ref ps);
m.WParam = dc;
shoulEndPaint = true;
}
else
{
dc = m.WParam;
}
var rgn = CreateRectRgn(innerInnerBorder.Left, innerInnerBorder.Top,
innerInnerBorder.Right, innerInnerBorder.Bottom);
SelectClipRgn(dc, rgn);
DefWndProc(ref m);
DeleteObject(rgn);
rgn = CreateRectRgn(clientRect.Left, clientRect.Top,
clientRect.Right, clientRect.Bottom);
SelectClipRgn(dc, rgn);
using (var g = Graphics.FromHdc(dc))
{
using (var b = new SolidBrush(buttonColor))
{
g.FillRectangle(b, dropDownRect);
}
using (var b = new SolidBrush(outerBorderColor))
{
g.FillPolygon(b, arrow);
}
using (var p = new Pen(innerBorderColor))
{
g.DrawRectangle(p, innerBorder);
g.DrawRectangle(p, innerInnerBorder);
}
using (var p = new Pen(outerBorderColor))
{
g.DrawRectangle(p, outerBorder);
}
}
if (shoulEndPaint)
EndPaint(Handle, ref ps);
DeleteObject(rgn);
}
else
base.WndProc(ref m);
}
private const int WM_PAINT = 0xF;
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int L, T, R, B;
}
[StructLayout(LayoutKind.Sequential)]
public struct PAINTSTRUCT
{
public IntPtr hdc;
public bool fErase;
public int rcPaint_left;
public int rcPaint_top;
public int rcPaint_right;
public int rcPaint_bottom;
public bool fRestore;
public bool fIncUpdate;
public int reserved1;
public int reserved2;
public int reserved3;
public int reserved4;
public int reserved5;
public int reserved6;
public int reserved7;
public int reserved8;
}
[DllImport("user32.dll")]
private static extern IntPtr BeginPaint(IntPtr hWnd,
[In, Out] ref PAINTSTRUCT lpPaint);
[DllImport("user32.dll")]
private static extern bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint);
[DllImport("gdi32.dll")]
public static extern int SelectClipRgn(IntPtr hDC, IntPtr hRgn);
[DllImport("user32.dll")]
public static extern int GetUpdateRgn(IntPtr hwnd, IntPtr hrgn, bool fErase);
public enum RegionFlags
{
ERROR = 0,
NULLREGION = 1,
SIMPLEREGION = 2,
COMPLEXREGION = 3,
}
[DllImport("gdi32.dll")]
internal static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateRectRgn(int x1, int y1, int x2, int y2);
}
I am creating a Speed Meter to measure my internet speed and stays at top of every app, then I how do I make Its background semi transparent or blurred something like this.
A WinForms Form with a Blur effect applied to it, using:
Windows 7 ⇒ DwmEnableBlurBehindWindow() DWM function.
Windows 10 / 11 ⇒ SetWindowCompositionAttribute() User32 function (undocumented)
How to apply the Blur Behind effect:
Windows 7:
The Window needs to register a rendering policy with DwmSetWindowAttribute(), setting the DWMWINDOWATTRIBUTE enumerator to NCRenderingPolicy and enable it, setting the DWMNCRENDERINGPOLICY enumerator to Enabled.
You may also want to override a Form's class WndProc; when a WM_DWMCOMPOSITIONCHANGED message is received (informing of the Aero composition state), to verify whether DWM Composition is enabled.
Before showing the Window interface, call DwmEnableBlurBehindWindow(), setting its DWM_BLURBEHIND structure relevant fields (dwFlags and fEnable) to 1.
Here, I'm calling it from the Form's constructor.
Windows 10 and Windows 11:
This code is using the not yet documented SetWindowCompositionAttribute() function.
The User32 function only requires the use of an internal structure, here called WinCompositionAttrData, where the Attribute member is set to DWMWINDOWATTRIBUTE.AccentPolicy and the Data member is set to the pointer of an internal structure, here called AccentPolicy, where the AccentState member is set to DWMACCENTSTATE.ACCENT_ENABLE_BLURBEHIND
DWM Compostion is always enabled. No need to verify it or override WndProc to handle a DWM Composition change.
It's more clear in code:
(As a note, the BlurBehind effect does not work if the Form has a white background color)
See this other question about a DwmExtendFrameIntoClientArea() implementation.
public partial class frmBlurBehind : Form
{
public frmBlurBehind()
{
InitializeComponent();
// Remove the border if this is meant to be a border-less Form
// FormBorderStyle = FormBorderStyle.None;
if (IsDWMCompositionEnabled()) {
if (Environment.OSVersion.Version.Major > 6) {
Dwm.Windows10EnableBlurBehind(Handle);
}
else {
Dwm.WindowEnableBlurBehind(Handle);
}
// Set Drop shadow of a border-less Form
if (FormBorderStyle == FormBorderStyle.None) {
Dwm.WindowBorderlessDropShadow(Handle, 2);
}
}
}
private bool IsDWMCompositionEnabled() =>
Environment.OSVersion.Version.Major >= 6 && Dwm.IsCompositionEnabled();
// Eventually, if this is a border-less Form, reapply the attribute if DWM Composition changes
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg) {
case Dwm.WM_DWMCOMPOSITIONCHANGED:
if (IsDWMCompositionEnabled()) {
Dwm.DWMNCRENDERINGPOLICY policy = Dwm.DWMNCRENDERINGPOLICY.Enabled;
Dwm.WindowSetAttribute(Handle, Dwm.DWMWINDOWATTRIBUTE.NCRenderingPolicy, (int)policy);
Dwm.WindowBorderlessDropShadow(Handle, 2);
m.Result = IntPtr.Zero;
}
break;
default:
break;
}
}
}
The Dwm class contain all interop functions and structures used above, plus a bunch of other methods that could become useful in this context
[SuppressUnmanagedCodeSecurity]
public class Dwm {
public const int WM_DWMCOMPOSITIONCHANGED = 0x031E;
public struct MARGINS {
public int leftWidth;
public int rightWidth;
public int topHeight;
public int bottomHeight;
public MARGINS(int LeftWidth, int RightWidth, int TopHeight, int BottomHeight)
{
leftWidth = LeftWidth;
rightWidth = RightWidth;
topHeight = TopHeight;
bottomHeight = BottomHeight;
}
public void NoMargins()
{
leftWidth = 0;
rightWidth = 0;
topHeight = 0;
bottomHeight = 0;
}
public void SheetOfGlass()
{
leftWidth = -1;
rightWidth = -1;
topHeight = -1;
bottomHeight = -1;
}
}
[Flags]
public enum DWM_BB {
Enable = 1,
BlurRegion = 2,
TransitionOnMaximized = 4
}
// https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
public enum DWMWINDOWATTRIBUTE : uint {
NCRenderingEnabled = 1, //Get atttribute
NCRenderingPolicy, //Enable or disable non-client rendering
TransitionsForceDisabled,
AllowNCPaint,
CaptionButtonBounds, //Get atttribute
NonClientRtlLayout,
ForceIconicRepresentation,
Flip3DPolicy,
ExtendedFrameBounds, //Get atttribute
HasIconicBitmap,
DisallowPeek,
ExcludedFromPeek,
Cloak,
Cloaked, //Get atttribute. Returns a DWMCLOACKEDREASON
FreezeRepresentation,
PassiveUpdateMode,
UseHostBackDropBrush,
AccentPolicy = 19, // Win 10 (undocumented)
ImmersiveDarkMode = 20, // Win 11 22000
WindowCornerPreference = 33, // Win 11 22000
BorderColor, // Win 11 22000
CaptionColor, // Win 11 22000
TextColor, // Win 11 22000
VisibleFrameBorderThickness, // Win 11 22000
SystemBackdropType // Win 11 22621
}
public enum DWMCLOACKEDREASON : uint {
DWM_CLOAKED_APP = 0x0000001, //cloaked by its owner application.
DWM_CLOAKED_SHELL = 0x0000002, //cloaked by the Shell.
DWM_CLOAKED_INHERITED = 0x0000004 //inherited from its owner window.
}
public enum DWMNCRENDERINGPOLICY : uint {
UseWindowStyle, // Enable/disable non-client rendering based on window style
Disabled, // Disabled non-client rendering; window style is ignored
Enabled, // Enabled non-client rendering; window style is ignored
};
public enum DWMACCENTSTATE {
ACCENT_DISABLED = 0,
ACCENT_ENABLE_GRADIENT = 1,
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
ACCENT_ENABLE_BLURBEHIND = 3,
ACCENT_INVALID_STATE = 4
}
[Flags]
public enum CompositionAction : uint {
DWM_EC_DISABLECOMPOSITION = 0,
DWM_EC_ENABLECOMPOSITION = 1
}
// Values designating how Flip3D treats a given window.
enum DWMFLIP3DWINDOWPOLICY : uint {
Default, // Hide or include the window in Flip3D based on window style and visibility.
ExcludeBelow, // Display the window under Flip3D and disabled.
ExcludeAbove, // Display the window above Flip3D and enabled.
};
public enum ThumbProperties_dwFlags : uint {
RectDestination = 0x00000001,
RectSource = 0x00000002,
Opacity = 0x00000004,
Visible = 0x00000008,
SourceClientAreaOnly = 0x00000010
}
[StructLayout(LayoutKind.Sequential)]
public struct AccentPolicy {
public DWMACCENTSTATE AccentState;
public int AccentFlags;
public int GradientColor;
public int AnimationId;
public AccentPolicy(DWMACCENTSTATE accentState, int accentFlags, int gradientColor, int animationId)
{
AccentState = accentState;
AccentFlags = accentFlags;
GradientColor = gradientColor;
AnimationId = animationId;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct DWM_BLURBEHIND {
public DWM_BB dwFlags;
public int fEnable;
public IntPtr hRgnBlur;
public int fTransitionOnMaximized;
public DWM_BLURBEHIND(bool enabled)
{
dwFlags = DWM_BB.Enable;
fEnable = (enabled) ? 1 : 0;
hRgnBlur = IntPtr.Zero;
fTransitionOnMaximized = 0;
}
public Region Region => Region.FromHrgn(hRgnBlur);
public bool TransitionOnMaximized {
get => fTransitionOnMaximized > 0;
set {
fTransitionOnMaximized = (value) ? 1 : 0;
dwFlags |= DWM_BB.TransitionOnMaximized;
}
}
public void SetRegion(Graphics graphics, Region region)
{
hRgnBlur = region.GetHrgn(graphics);
dwFlags |= DWM_BB.BlurRegion;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct WinCompositionAttrData {
public DWMWINDOWATTRIBUTE Attribute;
public IntPtr Data; //Will point to an AccentPolicy struct, where Attribute will be DWMWINDOWATTRIBUTE.AccentPolicy
public int SizeOfData;
public WinCompositionAttrData(DWMWINDOWATTRIBUTE attribute, IntPtr data, int sizeOfData)
{
Attribute = attribute;
Data = data;
SizeOfData = sizeOfData;
}
}
private static int GetBlurBehindPolicyAccentFlags()
{
int drawLeftBorder = 20;
int drawTopBorder = 40;
int drawRightBorder = 80;
int drawBottomBorder = 100;
return (drawLeftBorder | drawTopBorder | drawRightBorder | drawBottomBorder);
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa969508(v=vs.85).aspx
[DllImport("dwmapi.dll")]
internal static extern int DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind);
[DllImport("dwmapi.dll", PreserveSig = false)]
public static extern void DwmEnableComposition(CompositionAction uCompositionAction);
//https://msdn.microsoft.com/it-it/library/windows/desktop/aa969512(v=vs.85).aspx
[DllImport("dwmapi.dll")]
internal static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset);
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa969515(v=vs.85).aspx
[DllImport("dwmapi.dll")]
internal static extern int DwmGetWindowAttribute(IntPtr hwnd, DWMWINDOWATTRIBUTE attr, ref int attrValue, int attrSize);
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa969524(v=vs.85).aspx
[DllImport("dwmapi.dll")]
internal static extern int DwmSetWindowAttribute(IntPtr hwnd, DWMWINDOWATTRIBUTE attr, ref int attrValue, int attrSize);
[DllImport("User32.dll", SetLastError = true)]
internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WinCompositionAttrData data);
[DllImport("dwmapi.dll")]
internal static extern int DwmIsCompositionEnabled(ref int pfEnabled);
public static bool IsCompositionEnabled()
{
int pfEnabled = 0;
int result = DwmIsCompositionEnabled(ref pfEnabled);
return (pfEnabled == 1) ? true : false;
}
public static bool IsNonClientRenderingEnabled(IntPtr hWnd)
{
int gwaEnabled = 0;
int result = DwmGetWindowAttribute(hWnd, DWMWINDOWATTRIBUTE.NCRenderingEnabled, ref gwaEnabled, sizeof(int));
return gwaEnabled == 1;
}
public static bool WindowSetAttribute(IntPtr hWnd, DWMWINDOWATTRIBUTE attribute, int attributeValue)
{
int result = DwmSetWindowAttribute(hWnd, attribute, ref attributeValue, sizeof(int));
return (result == 0);
}
public static void Windows10EnableBlurBehind(IntPtr hWnd)
{
DWMNCRENDERINGPOLICY policy = DWMNCRENDERINGPOLICY.Enabled;
WindowSetAttribute(hWnd, DWMWINDOWATTRIBUTE.NCRenderingPolicy, (int)policy);
AccentPolicy accPolicy = new AccentPolicy() {
AccentState = DWMACCENTSTATE.ACCENT_ENABLE_BLURBEHIND,
};
int accentSize = Marshal.SizeOf(accPolicy);
IntPtr accentPtr = Marshal.AllocHGlobal(accentSize);
Marshal.StructureToPtr(accPolicy, accentPtr, false);
var data = new WinCompositionAttrData(DWMWINDOWATTRIBUTE.AccentPolicy, accentPtr, accentSize);
SetWindowCompositionAttribute(hWnd, ref data);
Marshal.FreeHGlobal(accentPtr);
}
public static bool WindowEnableBlurBehind(IntPtr hWnd)
{
DWMNCRENDERINGPOLICY policy = DWMNCRENDERINGPOLICY.Enabled;
WindowSetAttribute(hWnd, DWMWINDOWATTRIBUTE.NCRenderingPolicy, (int)policy);
DWM_BLURBEHIND dwm_BB = new DWM_BLURBEHIND(true);
int result = DwmEnableBlurBehindWindow(hWnd, ref dwm_BB);
return result == 0;
}
public static bool WindowExtendIntoClientArea(IntPtr hWnd, MARGINS margins)
{
// Extend frame on the bottom of client area
int result = DwmExtendFrameIntoClientArea(hWnd, ref margins);
return result == 0;
}
public static bool WindowBorderlessDropShadow(IntPtr hWnd, int shadowSize)
{
MARGINS margins = new MARGINS(0, shadowSize, 0, shadowSize);
int result = DwmExtendFrameIntoClientArea(hWnd, ref margins);
return result == 0;
}
public static bool WindowSheetOfGlass(IntPtr hWnd)
{
MARGINS margins = new MARGINS();
//Margins set to All:-1 - Sheet Of Glass effect
margins.SheetOfGlass();
int result = DwmExtendFrameIntoClientArea(hWnd, ref margins);
return result == 0;
}
public static bool WindowDisableRendering(IntPtr hWnd)
{
int ncrp = (int)DWMNCRENDERINGPOLICY.Disabled;
// Disable non-client area rendering on the window.
int result = DwmSetWindowAttribute(hWnd, DWMWINDOWATTRIBUTE.NCRenderingPolicy, ref ncrp, sizeof(int));
return result == 0;
}
} //DWM
This is a sample result with a Form.BackColor set to MidnightBlue:
Window Selected Windows Unselected
With some controls on it:
Hi I am getting an 'Inconsistent accessibility: parameter type'clsWebCamArgs' is less accessible than delegate 'ctrlCamera.WebCamEventHandler'. Can anyone give any insight as to whats wrong with the code below.
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace PayrollSystem
{
[ToolboxBitmap(typeof(ctrlCamera), "Camera.ico")] // toolbox bitmap
[Designer("Sytem.Windows.Forms.Design.ParentControlDesigner,System.Design", typeof(IDesigner))] // make composite
public partial class ctrlCamera : UserControl
{
public ctrlCamera(){InitializeComponent();}
// property variables
private int m_TimeToCapture_milliseconds = 100;
private int m_Width = 320;
private int m_Height = 240;
private int mCapHwnd;
private ulong m_FrameNumber = 0;
// global variables to make the video capture go faster
private PayrollSystem.clsWebCamArgs x = new PayrollSystem.clsWebCamArgs();
private IDataObject tempObj;
private Image tempImg;
private bool bStopped = true;
// event delegate
public delegate void WebCamEventHandler(object source, PayrollSystem.clsWebCamArgs e);
// fired when a new image is captured
public event WebCamEventHandler ImageCaptured;
#region API Declarations
[DllImport("user32", EntryPoint = "SendMessage")]
public static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);
[DllImport("avicap32.dll", EntryPoint = "capCreateCaptureWindowA")]
public static extern int capCreateCaptureWindowA(string lpszWindowName, int dwStyle, int X, int Y, int nWidth, int nHeight, int hwndParent, int nID);
[DllImport("user32", EntryPoint = "OpenClipboard")]
public static extern int OpenClipboard(int hWnd);
[DllImport("user32", EntryPoint = "EmptyClipboard")]
public static extern int EmptyClipboard();
[DllImport("user32", EntryPoint = "CloseClipboard")]
public static extern int CloseClipboard();
#endregion
#region API Constants
public const int WM_USER = 1024;
public const int WM_CAP_CONNECT = 1034;
public const int WM_CAP_DISCONNECT = 1035;
public const int WM_CAP_GET_FRAME = 1084;
public const int WM_CAP_COPY = 1054;
public const int WM_CAP_START = WM_USER;
public const int WM_CAP_DLG_VIDEOFORMAT = WM_CAP_START + 41;
public const int WM_CAP_DLG_VIDEOSOURCE = WM_CAP_START + 42;
public const int WM_CAP_DLG_VIDEODISPLAY = WM_CAP_START + 43;
public const int WM_CAP_GET_VIDEOFORMAT = WM_CAP_START + 44;
public const int WM_CAP_SET_VIDEOFORMAT = WM_CAP_START + 45;
public const int WM_CAP_DLG_VIDEOCOMPRESSION = WM_CAP_START + 46;
public const int WM_CAP_SET_PREVIEW = WM_CAP_START + 50;
#endregion
#region Control Properties
public int TimeToCapture_milliseconds
{
get { return m_TimeToCapture_milliseconds; }
set { m_TimeToCapture_milliseconds = value; }
}
public int CaptureHeight
{
get { return m_Height; }
set { m_Height = value; }
}
public int CaptureWidth
{
get { return m_Width; }
set { m_Width = value; }
}
public ulong FrameNumber
{
get { return m_FrameNumber; }
set { m_FrameNumber = value; }
}
#endregion
#region Start and Stop Capture Functions
public void Start(ulong FrameNum)
{
try
{
// for safety, call stop, just in case we are already running
this.Stop();
// setup a capture window
mCapHwnd = capCreateCaptureWindowA("WebCap", 0, 0, 0, m_Width, m_Height, this.Handle.ToInt32(), 0);
// connect to the capture device
Application.DoEvents();
SendMessage(mCapHwnd, WM_CAP_CONNECT, 0, 0);
SendMessage(mCapHwnd, WM_CAP_SET_PREVIEW, 0, 0);
// set the frame number
m_FrameNumber = FrameNum;
// set the timer information
this.timer1.Interval = m_TimeToCapture_milliseconds;
bStopped = false;
this.timer1.Start();
}
catch (Exception excep)
{
MessageBox.Show("An error ocurred while starting the video capture. Check that your webcamera is connected properly and turned on.\r\n\n" + excep.Message);
this.Stop();
}
}
public void Stop()
{
try
{
// stop the timer
bStopped = true;
this.timer1.Stop();
// disconnect from the video source
Application.DoEvents();
SendMessage(mCapHwnd, WM_CAP_DISCONNECT, 0, 0);
}
catch (Exception excep)
{ // don't raise an error here.
}
}
#endregion
#region Video Capture Code
private void timer1_Tick(object sender, EventArgs e)
{
try
{
// pause the timer
this.timer1.Stop();
// get the next frame;
SendMessage(mCapHwnd, WM_CAP_GET_FRAME, 0, 0);
// copy the frame to the clipboard
SendMessage(mCapHwnd, WM_CAP_COPY, 0, 0);
// paste the frame into the event args image
if (ImageCaptured != null)
{
// get from the clipboard
tempObj = Clipboard.GetDataObject();
tempImg = (Bitmap)tempObj.GetData(DataFormats.Bitmap);
x.WebCamImage = tempImg.GetThumbnailImage(m_Width, m_Height, null, IntPtr.Zero);
// raise the event
this.ImageCaptured(this, x);
}
// restart the timer
Application.DoEvents();
if (!bStopped)
this.timer1.Start();
}
catch (Exception excep)
{
MessageBox.Show("An error ocurred while capturing the video image. The video capture will now be terminated.\r\n\n" + excep.Message);
this.Stop(); // stop the process
}
}
#endregion
}
}
You declare
public delegate void WebCamEventHandler(object source, PayrollSystem.clsWebCamArgs e);
as public, meaning it should be accessible from everywhere (even from other assemblies).
But it seems (though I can't find it the code you show) that PayrollSystem.clsWebCamArgs are not declared public, and so not as accessible as the delegate.
So the compiler is giving you an error, because it's not possible to access WebCamEventHandler from other assemblies if the type of one of it's arguments is not accessible.
To solve this, you can either change the declaration of WebCamEventHandler to internal or the declaration of clsWebCamArgs to public.
How to change the font of all the contents of a richtextbox without losing formatting?
I am trying to use
rtb.SelectAll();
rtb.SelectionFont = new Font(fontName,...);
but the font constructor has to take besides the font type either the font style (bold, italics, ...) or font size.
So using this would change the style/size of all the content of the richtextbox.
Of course the same applies for any selection in the richtextbox.
This is a RichTextBox that I have used in the past. It's spliced together from code found here at Stack Overflow and the internet at large:
public class RichBox : RichTextBox {
private const UInt32 CFM_BOLD = 0x00000001;
private const UInt32 CFM_ITALIC = 0x00000002;
private const UInt32 CFM_UNDERLINE = 0x00000004;
private const UInt32 CFM_STRIKE = 0x00000008;
private const UInt32 CFM_FACE = 0x20000000;
private const UInt32 CFM_SIZE = 0x80000000;
private const int WM_PAINT = 0xF;
private const int WM_SETREDRAW = 0xB;
private const int WM_USER = 0x400;
private const int EM_SETCHARFORMAT = (WM_USER + 68);
private const int SCF_SELECTION = 0x0001;
private const int EM_GETEVENTMASK = WM_USER + 59;
private const int EM_SETEVENTMASK = WM_USER + 69;
private const int EM_GETSCROLLPOS = WM_USER + 221;
private const int EM_SETSCROLLPOS = WM_USER + 222;
[StructLayout(LayoutKind.Sequential)]
private struct CHARFORMAT {
public int cbSize;
public uint dwMask;
public uint dwEffects;
public int yHeight;
public int yOffset;
public int crTextColor;
public byte bCharSet;
public byte bPitchAndFamily;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] szFaceName;
public short wWeight;
public short sSpacing;
public int crBackColor;
public int LCID;
public uint dwReserved;
public short sStyle;
public short wKerning;
public byte bUnderlineType;
public byte bAnimation;
public byte bRevAuthor;
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref CHARFORMAT lParam);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, ref Point lParam);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, IntPtr lParam);
private bool frozen = false;
private Point lastScroll = Point.Empty;
private IntPtr lastEvent = IntPtr.Zero;
private int lastIndex = 0;
private int lastWidth = 0;
protected override CreateParams CreateParams {
get {
var cp = base.CreateParams;
if (LoadLibrary("msftedit.dll") != IntPtr.Zero) {
cp.ClassName = "RICHEDIT50W";
}
return cp;
}
}
[Browsable(false)]
[DefaultValue(typeof(bool), "False")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool FreezeDrawing {
get { return frozen; }
set {
if (value != frozen) {
frozen = value;
if (frozen) {
this.SuspendLayout();
SendMessage(this.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
SendMessage(this.Handle, EM_GETSCROLLPOS, 0, ref lastScroll);
lastEvent = SendMessage(this.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
lastIndex = this.SelectionStart;
lastWidth = this.SelectionLength;
} else {
this.Select(lastIndex, lastWidth);
SendMessage(this.Handle, EM_SETEVENTMASK, 0, lastEvent);
SendMessage(this.Handle, EM_SETSCROLLPOS, 0, ref lastScroll);
SendMessage(this.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
this.Invalidate();
this.ResumeLayout();
}
}
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Font CurrentFont {
get {
Font result = this.Font;
if (this.SelectionLength == 0) {
result = SelectionFont;
} else {
using (RichBox rb = new RichBox()) {
rb.FreezeDrawing = true;
rb.SelectAll();
rb.SelectedRtf = this.SelectedRtf;
rb.Select(0, 1);
result = rb.SelectionFont;
}
}
return result;
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public string SelectionFontName {
get { return CurrentFont.FontFamily.Name; }
set {
CHARFORMAT cf = new CHARFORMAT();
cf.cbSize = Marshal.SizeOf(cf);
cf.szFaceName = new char[32];
cf.dwMask = CFM_FACE;
value.CopyTo(0, cf.szFaceName, 0, Math.Min(31, value.Length));
IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf));
Marshal.StructureToPtr(cf, lParam, false);
SendMessage(this.Handle, EM_SETCHARFORMAT, SCF_SELECTION, lParam);
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public float SelectionFontSize {
get { return CurrentFont.Size; }
set {
CHARFORMAT cf = new CHARFORMAT();
cf.cbSize = Marshal.SizeOf(cf);
cf.dwMask = CFM_SIZE;
cf.yHeight = Convert.ToInt32(value * 20);
IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf));
Marshal.StructureToPtr(cf, lParam, false);
SendMessage(this.Handle, EM_SETCHARFORMAT, SCF_SELECTION, lParam);
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool SelectionBold {
get { return CurrentFont.Bold; }
set {
CHARFORMAT cf = new CHARFORMAT();
cf.cbSize = Marshal.SizeOf(cf);
cf.dwMask = CFM_BOLD;
cf.dwEffects = value ? CFM_BOLD : 0;
IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf));
Marshal.StructureToPtr(cf, lParam, false);
SendMessage(this.Handle, EM_SETCHARFORMAT, SCF_SELECTION, lParam);
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool SelectionItalic {
get { return CurrentFont.Italic; }
set {
CHARFORMAT cf = new CHARFORMAT();
cf.cbSize = Marshal.SizeOf(cf);
cf.dwMask = CFM_ITALIC;
cf.dwEffects = value ? CFM_ITALIC : 0;
IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf));
Marshal.StructureToPtr(cf, lParam, false);
SendMessage(this.Handle, EM_SETCHARFORMAT, SCF_SELECTION, lParam);
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool SelectionStrikeout {
get { return CurrentFont.Strikeout; }
set {
CHARFORMAT cf = new CHARFORMAT();
cf.cbSize = Marshal.SizeOf(cf);
cf.dwMask = CFM_STRIKE;
cf.dwEffects = value ? CFM_STRIKE : 0;
IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf));
Marshal.StructureToPtr(cf, lParam, false);
SendMessage(this.Handle, EM_SETCHARFORMAT, SCF_SELECTION, lParam);
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool SelectionUnderline {
get { return CurrentFont.Underline; }
set {
CHARFORMAT cf = new CHARFORMAT();
cf.cbSize = Marshal.SizeOf(cf);
cf.dwMask = CFM_UNDERLINE;
cf.dwEffects = value ? CFM_UNDERLINE : 0;
IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf));
Marshal.StructureToPtr(cf, lParam, false);
SendMessage(this.Handle, EM_SETCHARFORMAT, SCF_SELECTION, lParam);
}
}
}
It adds new properties such as SelectionBold, SelectionItalic, etc. where you can apply the attribute and not lose the other formatting of the text.
You can pass the new font name while keeping other values intact by using the existing richtextbox font properties. For changing only font name of a selected text, you need to do:
if (rtb.SelectionFont !=null)
rtb.SelectionFont = new Font(fontName, rtb.SelectionFont.Size, rtb.SelectionFont.Style);
Note that above code will only work, if all the selected text has same formatting (font size, style etc). This is detected by checking the SelectionFont property first, it will be null if the selection contains a mix of styles.
Now to change the font name of all the contents of richtextbox while keeping other formatting intact, you need to loop through all the characters of the richtextbox and apply font name one by one.
for (int i = 0; i < rtb.TextLength; i++)
{
rtb.Select(i, 1);
rtb.SelectionFont = new Font(fontName, rtb.SelectionFont.Size, rtb.SelectionFont.Style);
}
You can pass in new values for whatever parameter you want and use the rtb properties to preserve other values. For example, if you want to change the font family but want to preserve the font size, this is what you'd do:
rtb.SelectionFont = new Font(fontName, rtb.Font.Size);
This will change the SelectionFont family to fontName but preserves the font size. You can follow the same pattern for other overloads.
Private Sub changeFont(ByVal fontz As FontStyle, getStr As RichTextBox)
Dim currentFont As System.Drawing.Font = txt.SelectionFont
Dim newFontStyle As System.Drawing.FontStyle
newFontStyle = fontz
getStr.SelectionFont = New Font(currentFont.FontFamily, currentFont.Size, newFontStyle)
End Sub
This will change the font property of the selected text.
Sample:
changeFont(FontStyle.Italic, [textbox_name])
Changing font for richtextbox without losing formatting
private void Change_RichTextBox_FontName(string fontName )
{
if (richTextBox1.TextLength ==0)
{
return;
}
richTextBox1.Select(0, 1);
var lastTextColor = richTextBox1.SelectionColor;
var lastFontStyle = richTextBox1.SelectionFont.Style;
var lastFontSize = richTextBox1.SelectionFont.Size;
var lastSelectionStart = 0;
for (int i=1; i < richTextBox1.TextLength;i++)
{
richTextBox1.Select(i, 1);
var selColor = richTextBox1.SelectionColor;
var selStyle = richTextBox1.SelectionFont.Style;
var selSize = richTextBox1.SelectionFont.Size;
if (selColor != lastTextColor ||
selStyle != lastFontStyle ||
selSize != lastFontSize ||
i== richTextBox1.TextLength-1)
{
richTextBox1.Select(lastSelectionStart, i - lastSelectionStart);
richTextBox1.SelectionColor = lastTextColor;
richTextBox1.SelectionFont =
new Font(fontName, lastFontSize, lastFontStyle);
lastTextColor = selColor;
lastFontStyle = selStyle;
lastFontSize = selSize;
lastSelectionStart = i;
}
}
}