Show checkbox to root node in treeview C# - c#

I wish to have checkbox to the root nodes and not the child nodes of the Treeview control.
- [x]Pie Chart report
- Sales report
- Sales Projection report
- Linear Sales report
- [x]Surface Chart report
- Sales report
- Sales Projection report
- Linear Sales report
- [x]Caligraph report
- Sales report
- Sales Projection report
- Linear Sales report
To achieve this I have done change in my regular treeview control
tvreport is the Treeview Control
this.tvreport.DrawNode += new System.Windows.Forms.DrawTreeNodeEventHandler(tvreport_DrawNode);
this.tvreport.ShowLines = true;
this.tvreport.DrawMode = System.Windows.Forms.TreeViewDrawMode.OwnerDrawAll;
private void tvreport_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
if (IsContactNode(e.Node))
{
Color backColor, foreColor;
if ((e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected)
{
backColor = SystemColors.Highlight;
foreColor = SystemColors.HighlightText;
}
if ((e.State & TreeNodeStates.Hot) == TreeNodeStates.Hot)
{
backColor = SystemColors.HotTrack;
foreColor = SystemColors.HighlightText;
}
else
{
backColor = e.Node.BackColor;
foreColor = e.Node.ForeColor;
}
Rectangle newBounds = e.Node.Bounds;
newBounds.X = 60;
using (SolidBrush brush = new SolidBrush(backColor))
{
e.Graphics.FillRectangle(brush, e.Node.Bounds);
}
TextRenderer.DrawText(e.Graphics, e.Node.Text, this.tvreport.Font, e.Node.Bounds, foreColor, backColor);
if ((e.State & TreeNodeStates.Focused) == TreeNodeStates.Focused)
{
ControlPaint.DrawFocusRectangle(e.Graphics, e.Node.Bounds, foreColor, backColor);
}
e.DrawDefault = false;
}
else
{
e.DrawDefault = true;
tvContactList1.ShowRootLines = true;
tvContactList1.ShowLines = true;
}
}
private bool IsContactNode(TreeNode node)
{
return node.Parent != null;
}
After running the code have found that the root node is showing checkbox and childnodes is without checkbox [that is what I wish to have].
But the problem is the "Lines" that shows the hierarchy got disappeared. Now I want to populate those LINES. How can this be achieved.

TreeView Remove CheckBox by some Nodes is a possible solution.
This solution sends TVM_SETITEM-Message to the treeview.

Taking help of above link, I could achieve my requirement. In below code HideCheckBox() function should be called only after populating nodes in the treeview.
private const int TVIF_STATE = 0x8;
private const int TVIS_STATEIMAGEMASK = 0xF000;
private const int TV_FIRST = 0x1100;
private const int TVM_SETITEM = TV_FIRST + 63;
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Auto)]
private struct TVITEM
{
public int mask;
public IntPtr hItem;
public int state;
public int stateMask;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public IntPtr lParam;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam,
ref TVITEM lParam);
/// <summary>
/// Hides the checkbox for the specified node on a TreeView control.
/// </summary>
private void HideCheckBox(TreeView tvw, TreeNode node)
{
TVITEM tvi = new TVITEM();
tvi.hItem = node.Handle;
tvi.mask = TVIF_STATE;
tvi.stateMask = TVIS_STATEIMAGEMASK;
tvi.state = 0;
SendMessage(tvw.Handle, TVM_SETITEM, IntPtr.Zero, ref tvi);
}

Related

Is there a way to remove a check box from a root node in a Tree View Control in C#?

How do I remove the check boxes of the root nodes marked in the control given below?
This is what I have done so far.
private void SetRootNode(TreeNode node, int index)
{
IntPtr pt = treeViewFactMaps.Handle;
TvItem tvi = new TvItem();
tvi.hItem = node.Handle;
tvi.mask = TvifState;
tvi.stateMask = TvisStateimagemask;
tvi.state = index << 12;
SendMessage(node.TreeView.Handle, TvmSetitem, IntPtr.Zero,ref tvi);
}
The above method is called in a method called "InitializeFactMaps()".
treeViewFactMaps.Nodes.Clear();
bool selectFirstNode = true;
TreeNode firstNode = null;
foreach (DataSource ds in Sample.DataSources)
{
TreeNode root = treeViewFactMaps.Nodes.Add(ds.ItemName);
if (selectFirstNode)
{
firstNode= root;
selectFirstNode = false;
}
SetImage(root, false, false);
SetRootNode(root, 0);
foreach (DataSource childSource in ds.Children)
{
TreeNode leaf = root.Nodes.Add(childSource.ItemName);
leaf.Checked = Sample.UsedFactMaps.ContainsKey(childSource.FactId);
leaf.Tag = childSource.FactId;
SetImage(leaf, false, true);
}
}
treeViewFactMaps.CollapseAll();
treeViewFactMaps.Sort();
treeViewFactMaps.SelectedNode = firstNode;
Please note that I only want the check boxes of the root nodes to be removed. Not the child nodes.
I have used the solution posted for a similar question in Stack Overflow which can be found at TreeView Remove CheckBox by some Nodes, but it didn't work for me.
Here I have a solution that may help you. You can solve this problem via redrawing and
"SendMessage".
private void Form1_Load(object sender, EventArgs e)
{
this.treeView1.CheckBoxes = true;
this.treeView1.DrawMode = TreeViewDrawMode.OwnerDrawAll;
this.treeView1.DrawNode += new DrawTreeNodeEventHandler(treeViewGroupStatements_DrawNode);
// root1
TreeNode root1 = new TreeNode();
root1.Text = "root1";
TreeNode node11 = new TreeNode();
node11.Text = "11";
TreeNode node12 = new TreeNode();
node12.Text = "12";
root1.Nodes.Add(node11);
root1.Nodes.Add(node12);
treeView1.Nodes.Add(root1);
// root2
TreeNode root2 = new TreeNode();
root2.Text = "root2";
TreeNode node21 = new TreeNode();
node21.Text = "21";
TreeNode node22 = new TreeNode();
node22.Text = "22";
root2.Nodes.Add(node21);
root2.Nodes.Add(node22);
treeView1.Nodes.Add(root2);
// get all root nodes
foreach (var item in treeView1.Nodes)
{
if(((TreeNode)item).Level == 0)
{
HideList.Add(((TreeNode)item).Text, true);
}
}
}
// define a dictionary to store the root
public Dictionary<string, bool> HideList = new Dictionary<string, bool>();
private void treeViewGroupStatements_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
HideLevelOfTreeView(e.Node);
e.DrawDefault = true;
}
private void HideLevelOfTreeView(TreeNode tn)
{
if (HideList.ContainsKey(tn.Text))
HideCheckBox(tn.TreeView, tn);
}
private const int TVIF_STATE = 0x8;
private const int TVIS_STATEIMAGEMASK = 0xF000;
private const int TV_FIRST = 0x1100;
private const int TVM_SETITEM = TV_FIRST + 63;
private void HideCheckBox(TreeView tvw, TreeNode node)
{
TVITEM tvi = new TVITEM();
tvi.hItem = node.Handle;
tvi.mask = TVIF_STATE;
tvi.stateMask = TVIS_STATEIMAGEMASK;
tvi.state = 0;
SendMessage(tvw.Handle, TVM_SETITEM, IntPtr.Zero, ref tvi);
}
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Auto)]
private struct TVITEM
{
public int mask;
public IntPtr hItem;
public int state;
public int stateMask;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage; public int cChildren; public IntPtr lParam;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, ref TVITEM lParam);
The test result,

Move Horizental ScrollBar or a ListBox with a button in winforms

I am creating a winforms application in visual studio 2017, I am populating the list box using a List.
I set the multicolumn property to true. Since I have lots of strings in my list, there is a horizontal scrollbar appearing at the bottom of the box.
The application I am creating should be working on a tablet, so therefore the scroll bar is not easy to navigate using fingers.
My question is, is there a way to be able to control this scrollbar using a button ?
Yes, It is possible to control the behavior you are expecting with the help of Buttons.
To move from right to left -
private void btnLeft_Click(object sender, EventArgs e)
{
int visibleItemsInColumn = listBox1.ClientSize.Height / listBox1.ItemHeight; //No of items in each column. In this case - 5
listBox1.TopIndex = listBox1.TopIndex - visibleItemsInColumn;
}
To move from left to right -
private void btnRight_Click(object sender, EventArgs e)
{
int visibleItemsInColumn = listBox1.ClientSize.Height / listBox1.ItemHeight;
listBox1.TopIndex = listBox1.TopIndex + visibleItemsInColumn;
}
What it actually does is every time you click on button, It
increases/decreases the TopIndex by the total elements per columns. So
on each clicks, you move one column either left or right.
You can send WM_HSCROLL message to the ListBox to scroll it. To do so, you should first get the scroll position by calling GetScrollInfo methods.
The following code, scrolls the ListBox, 1 column to the right:
var info = new SCROLLINFO() { fMask = ScrollInfoMask.SIF_ALL };
GetScrollInfo(listBox1.Handle, SBOrientation.SB_HORZ, ref info);
var wparam = ((uint)(info.nPos + 1) << 16) | (SB_THUMBPOSITION & 0xffff);
SendMessage(listBox1.Handle, WM_HSCROLL, wparam, 0);
To scroll one column to the left, use info.nPos - 1.
Here are the declarations which you need:
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg,
uint wParam, uint lParam);
[StructLayout(LayoutKind.Sequential)]
struct SCROLLINFO {
public uint cbSize;
public ScrollInfoMask fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
}
public enum ScrollInfoMask : uint {
SIF_RANGE = 0x1,
SIF_PAGE = 0x2,
SIF_POS = 0x4,
SIF_DISABLENOSCROLL = 0x8,
SIF_TRACKPOS = 0x10,
SIF_ALL = (SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS),
}
[DllImport("user32.dll")]
private static extern bool GetScrollInfo(IntPtr hwnd,
SBOrientation fnBar, ref SCROLLINFO lpsi);
public enum SBOrientation : int {
SB_HORZ = 0x0,
SB_VERT = 0x1,
}
const uint WM_HSCROLL = 0x0114;
const uint SB_THUMBPOSITION = 4;

C# Moving programatically through rich textbox without cuting lines

I am doing a WinForms application with image/page scheme (the user is navigating between images by clicking at particular areas of the image). At one page I made rich textbox in which I wanted to load "rules" for the user to accept. Unfortunatelly, the rules are longer than the RTB, so I had to apply some kind of scrolling. I didn't want to let the user use a scrollbar - instead I made two buttons (up & down) by which the user can slide through the rules - user cant select or move text in any other way. Everything works great apart from one thing - after clicking the button, the text slides but part of the 1st and last shown lines are being cut (only part of the height of line is visible in rtb).
Below I list the code used for making such RTB (named "rules"):
rules.Clear();
rules.Cursor = Cursors.Default;
rules.ScrollBars = RichTextBoxScrollBars.Vertical;
rules.ReadOnly = true;
rules.KeyPress += rules_KeyPress;
rules.SelectionChanged += rules_SelectionChanged;
rules.Location = new System.Drawing.Point(450*this.Width/2000, 550*this.Height/1125);
Console.WriteLine(rules.Font);
float size = 16.5F;
rules.Font = new Font("Microsoft Sans Serif", size);
Console.WriteLine(rules.Font);
using (Graphics g = rules.CreateGraphics())
{
rules_line_height = Convert.ToInt32(g.MeasureString("A", rules.Font).Height); //getting the measure of the line
Console.WriteLine(rules_line_height);
}
int rules_h = 400 * this.Height / 1125; //height of the rtb
int lines_n = rules_h / rules_line_height; //max number of lines that can be fit in the height
rules.Size = new System.Drawing.Size(1000 * this.Width / 2000, lines_n*rules_line_height); //height of the rtb adjusted to fill full lines
rules.ForeColor = Color.White;
string fileName = "reg_pl.txt";
string line = "";
StreamReader file = new System.IO.StreamReader(Directory.GetCurrentDirectory() + "/reg/" + fileName, Encoding.GetEncoding("windows-1250"));
while ((line = file.ReadLine()) != null)
{
rules.AppendText(line + "\r\n");
}
file.Close();
rules_max = rules.GetMaxRange(); //getting max range of the rtb
rules_thumb = rules.GetThumb(); //getting size of visible part of the rtb
rules_loc = 0; // current location visible part compared to the whole rtb
rules.ScrollBars = RichTextBoxScrollBars.None;
rules.BorderStyle = BorderStyle.None;
rules.BackColor = Color.FromArgb(18, 25, 56);
rules.BringToFront();
I also used the custom_RTB class for getting max range and thumb of the RTB and scrolling to selected point of the RTB :
public class CustomRTB : System.Windows.Forms.RichTextBox
{
#region API Stuff
public struct SCROLLINFO
{
public int cbSize;
public int fMask;
public int min;
public int max;
public int nPage;
public int nPos;
public int nTrackPos;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
private static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("User32.Dll", EntryPoint = "PostMessageA")]
static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
[DllImport("user32")]
private static extern int GetScrollInfo(IntPtr hwnd, int nBar,
ref SCROLLINFO scrollInfo);
private const int SB_HORZ = 0x0;
private const int SB_VERT = 0x1;
#endregion
public int HorizontalPosition
{
get { return GetScrollPos((IntPtr)this.Handle, SB_HORZ); }
set { SetScrollPos((IntPtr)this.Handle, SB_HORZ, value, true); }
}
public int VerticalPosition
{
get { return GetScrollPos((IntPtr)this.Handle, SB_VERT); }
set { SetScrollPos((IntPtr)this.Handle, SB_VERT, value, true); }
}
public void ScrollTo(int Position)
{
SetScrollPos((IntPtr)this.Handle, 0x1, Position, true);
PostMessage((IntPtr)this.Handle, 0x115, 4 + 0x10000 * Position, 0);
}
public int GetMaxRange()
{
SCROLLINFO scrollInfo = new SCROLLINFO();
scrollInfo.cbSize = Marshal.SizeOf(scrollInfo);
//SIF_RANGE = 0x1, SIF_TRACKPOS = 0x10, SIF_PAGE= 0x2
scrollInfo.fMask = 0x10 | 0x1 | 0x2;
GetScrollInfo((IntPtr)this.Handle, 1, ref scrollInfo);//nBar = 1 -> VScrollbar
return scrollInfo.max;
}
public int GetThumb()
{
SCROLLINFO scrollInfo = new SCROLLINFO();
scrollInfo.cbSize = Marshal.SizeOf(scrollInfo);
//SIF_RANGE = 0x1, SIF_TRACKPOS = 0x10, SIF_PAGE= 0x2
scrollInfo.fMask = 0x10 | 0x1 | 0x2;
GetScrollInfo((IntPtr)this.Handle, 1, ref scrollInfo);//nBar = 1 -> VScrollbar
return scrollInfo.nPage;
}
public int GetCurPos()
{
SCROLLINFO scrollInfo = new SCROLLINFO();
scrollInfo.cbSize = Marshal.SizeOf(scrollInfo);
//SIF_RANGE = 0x1, SIF_TRACKPOS = 0x10, SIF_PAGE= 0x2
scrollInfo.fMask = 0x10 | 0x1 | 0x2;
GetScrollInfo((IntPtr)this.Handle, 1, ref scrollInfo);//nBar = 1 -> VScrollbar
return scrollInfo.nTrackPos;
}
public bool ReachedBottom()
{
SCROLLINFO scrollInfo = new SCROLLINFO();
scrollInfo.cbSize = Marshal.SizeOf(scrollInfo);
//SIF_RANGE = 0x1, SIF_TRACKPOS = 0x10, SIF_PAGE= 0x2
scrollInfo.fMask = 0x10 | 0x1 | 0x2;
GetScrollInfo((IntPtr)this.Handle, 1, ref scrollInfo);//nBar = 1 -> VScrollbar
return scrollInfo.max == scrollInfo.nTrackPos + scrollInfo.nPage;
}
}
And here are the "buttons" for moving the text up:
int lines = rules_thumb / rules_line_height; //getting max number of lines that can be visible
int new_thumb = lines * rules_line_height; //getting the move value so the lines won't be cut
if (rules_loc - (new_thumb) >= 0) //if moving by the move value will be still over 0, then proceed normally
{
rules_loc -= (new_thumb);
rules.ScrollTo(rules_loc);
}
else // if it will pass 0, the move to 0
{
rules_loc = 0;
rules.ScrollTo(rules_loc);
}
And down :
int lines = rules_thumb / rules_line_height;
int new_thumb = lines * rules_line_height;
if (rules_loc + (new_thumb) < rules_max)
{
rules_loc += (new_thumb);
rules.ScrollTo(rules_loc);
}
else
{
rules.ScrollTo(rules_max);
}
What I think I am doing wrong is measuring the lines heigh at given font and setting the good size for the RTB. Does anyone had similar problem and can somehow assist me with it?
Thank you in advance for the answer.
Best regards,
Sarachiel

how to get wrapped lines from multiline textbox?

In my windows.forms c# application, I have a multi-line textbox with WordWrap = true. After I set Text property to a long string, I need to get all lines produced by wrapping. It is not the same as Lines[] property, because my text does not include new line characters.
I have found solutions using graphics MeasureString function but it seems a little bit extra work considering that the textbox control already did the wrapping - why should I do the same work again?
Is there any way to get the lines into which the textbox wraps the text?
Thank you
Can you check the below solution,
public Form1()
{
InitializeComponent();
textBox1.Text = "This is my text where I want to check how I can get wrapped content as seperate lines automatically !! This is my text which I want to check how I can get wrapped content as seperate lines automatically !!";
}
private void button1_Click(object sender, EventArgs e)
{
bool continueProcess = true;
int i = 1; //Zero Based So Start from 1
int j = 0;
List<string> lines = new List<string>();
while (continueProcess)
{
var index = textBox1.GetFirstCharIndexFromLine(i);
if (index != -1)
{
lines.Add(textBox1.Text.Substring(j, index - j));
j = index;
i++;
}
else
{
lines.Add(textBox1.Text.Substring(j, textBox1.Text.Length - j));
continueProcess = false;
}
}
foreach(var item in lines)
{
MessageBox.Show(item);
}
}
GetFirstCharIndexFromLine Reference
Line numbering in the text box starts at zero. If the lineNumber
parameter is greater than the last line in the text box,
GetFirstCharIndexFromLine returns -1.
GetFirstCharIndexFromLine returns the first character index of a
physical line. The physical line is the displayed line, not the
assigned line. The number of displayed lines can be greater than the
number of assigned lines due to word wrap. For example, if you assign
two long lines to a RichTextBox control and set Multiline and WordWrap
to true, the two long assigned lines result in four physical (or
displayed lines).
A little pinvoking would work:
private const UInt32 EM_GETLINECOUNT = 0xba;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
private void button1_Click(object sender, EventArgs e) {
int numLines = SendMessage(textBox1.Handle,
EM_GETLINECOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32()
MessageBox.Show(numLines.ToString());
}
REVISED ANSWER
I checked the Win32 APIs again and realized it could be done easily. I wrote an extension method so you can do it even easier:
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
static class TextBoxExtensions
{
private const uint EM_FMTLINES = 0x00C8;
private const uint WM_GETTEXT = 0x000D;
private const uint WM_GETTEXTLENGTH = 0x000E;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, StringBuilder lParam);
public static string[] GetWrappedLines(this TextBox textBox)
{
var handle = textBox.Handle;
SendMessage(handle, EM_FMTLINES, 1, IntPtr.Zero);
var size = SendMessage(handle, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero).ToInt32();
if (size > 0)
{
var builder = new StringBuilder(size + 1);
SendMessage(handle, WM_GETTEXT, builder.Capacity, builder);
return builder.ToString().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
}
return new string[0];
}
}
}
usage:
var lines = textBox1.GetWrappedLines();
ORIGINAL ANSWER
WinForm TextBox is actually a wrapper of Windows GDI edit control, which handles text wrapping natively. That being said, even if the TextBox keeps an array of wrapped lines, it is not exposed by public API, not even brought to managed environment (which, if it did, can however be retrieved with reflection). So your best bet is still MeasureString.
To check if particular line is wrapped or not, here is the GDI Function you need to use:
1. [DllImport("user32.dll")]
static extern int DrawText(IntPtr hdc, string lpStr, int nCount, ref Dimension lpRect, int wFormat);
Here are what you need to get things done:
public enum DrawTextFlags
{
CalculateArea = 0x00000400,
WordBreak = 0x00000010,
TextBoxControl = 0x00002000,
Top = 0x00000000,
Left = 0x00000000,
HorizontalCenter = 0x00000001,
Right = 0x00000002,
VerticalCenter = 0x00000004,
Bottom = 0x00000008,
SingleLine = 0x00000020,
ExpandTabs = 0x00000040,
TabStop = 0x00000080,
NoClipping = 0x00000100,
ExternalLeading = 0x00000200,
NoPrefix = 0x00000800,
Internal = 0x00001000,
PathEllipsis = 0x00004000,
EndEllipsis = 0x00008000,
WordEllipsis = 0x00040000,
ModifyString = 0x00010000,
RightToLeft = 0x00020000,
NoFullWidthCharacterBreak = 0x00080000,
HidePrefix = 0x00100000,
PrefixOnly = 0x00200000,
NoPadding = 0x10000000,
}
[StructLayout(LayoutKind.Sequential)]
public struct Dimension
{
public int Left, Top, Right, Bottom;
public Dimension(int left, int top, int right, int bottom)
{
this.Left = left;
this.Right = right;
this.Top = top;
this.Bottom = bottom;
}
public Dimension(Rectangle r)
{
this.Left = r.Left;
this.Top = r.Top;
this.Bottom = r.Bottom;
this.Right = r.Right;
}
public static implicit operator Rectangle(Dimension rc)
{
return Rectangle.FromLTRB(rc.Left, rc.Top, rc.Right, rc.Bottom);
}
public static implicit operator Dimension(Rectangle rc)
{
return new Dimension(rc);
}
public static Dimension Default
{
get { return new Dimension(0, 0, 1, 1); }
}
}
So to know whether a particular line is wrapped or not, you would call the function like this:
Dimension rc = new Dimension(0,0,2,2);
var flag = DrawTextFlags.CalculateArea | DrawTextFlags.TextBoxControl | DrawTextFlags.WordBreak;
DrawText(hdc, line, line.length, ref rc, (int)flag);
Now if height of rc you get after executing this function is greater then your font height or tmHeight if you use TextMetric (that is what minimum required for a line to fit vertically) you can safely assume your line is wrapped.
Apart from this,
You can use the following function as an alternative approach:
static extern bool GetTextExtentExPoint(IntPtr hDc, string str, int nLength,
int nMaxExtent, int[] lpnFit, int[] alpDx, ref Size size);

Using winapi to change text on color dialog but 2 static controls have the same control id

Let me start by saying that I am no stranger to using winapi calls to manipulate other windows, but this is the first time I have seen a window that has two identical control IDs. It seems that the color dialog hasn't changed much between windows versions and I can confirm that this behavior exists on all color dialogs from Windows Vista through to Windows 10 (possibly exists in win xp and lower as well but I can't be bothered to check).
What I am attempting to do is use winapi calls to localize the text in a color dialog control in C#. The best way I have found to do this is to use GetDlgItem() to get a handle to the control I wish to change and then use SetWindowText() to actually change the text. This works great for all controls on the color dialog except for the 'Basic colors:' and 'Custom colors:' labels, which both have a control ID of 0xFFFF (decimal value: 65535).
I use an app called WinID to do this type of work (I find it much easier than using Spy++) and you can see from the screenshots below that the ID of the two text labels do in-fact register as the same ID.
NOTE: I have
tested this using Spy++ and of course I get the same values as shown below:
I would like to know 2 things:
How is it possible for 2 controls to have the same control id?
Is there a 'better way' to get a handle to a control from an external dialog/app using winapi calls? Please keep in mind that using something like FindWindowEx(nColorDialogHandle, IntPtr.Zero, "Static", "&custom colors:"); works, but is not useful to me because I must be able to find the handle without relying on the text in English since this must also work on color dialogs from a non-English version of Windows.
Below is some sample code to demonstrate how I am currently able to change the text on a color dialog. I am happy with the code except that I am unable to get a direct handle to the 'Custom colors:' label since using GetDlgItem() with the control id of 0xFFFF seems to return a handle to the first instance of the control with that ID (in this case it always returns a handle to the 'Basic colors:' label). The only way I am able to get the 'Custom colors:' handle is by using an indirect method of looping through all controls on the color dialog until I find one with text that has not already been changed. This works fine but I would like to know if there is a more direct way to get this handle without looping through controls:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// Open the color dialog before the form actually loads
ColorDialogEx oColorDialog = new ColorDialogEx(this.CreateGraphics());
oColorDialog.FullOpen = true;
oColorDialog.ShowDialog();
}
}
public class ColorDialogEx : ColorDialog
{
private const Int32 WM_INITDIALOG = 0x0110; // Windows Message Constant
private Graphics oGraphics;
private const uint GW_HWNDLAST = 1;
private const uint GW_HWNDPREV = 3;
private string sColorPickerText = "1-Color Picker";
private string sBasicColorsText = "2-Basic colors:";
private string sDefineCustomColorsButtonText = "3-Define Custom Colors >>";
private string sOKButtonText = "4-OK";
private string sCancelButtonText = "5-Cancel";
private string sAddToCustomColorsButtonText = "6-Add to Custom Colors";
private string sColorText = "7-Color";
private string sSolidText = "|8-Solid";
private string sHueText = "9-Hue:";
private string sSatText = "10-Sat:";
private string sLumText = "11-Lum:";
private string sRedText = "12-Red:";
private string sGreenText = "13-Green:";
private string sBlueText = "14-Blue:";
private string sCustomColorsText = "15-Custom colors:";
// WinAPI definitions
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool SetWindowText(IntPtr hWnd, string text);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
[DllImport("user32.dll")]
public static extern long GetWindowRect(int hWnd, ref Rectangle lpRect);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetTitleBarInfo(IntPtr hwnd, ref TITLEBARINFO pti);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll")]
private static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int GetClassName(IntPtr hWnd, System.Text.StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[StructLayout(LayoutKind.Sequential)]
struct TITLEBARINFO
{
public const int CCHILDREN_TITLEBAR = 5;
public uint cbSize;
public RECT rcTitleBar;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CCHILDREN_TITLEBAR + 1)]
public uint[] rgstate;
}
[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);
}
}
public ColorDialogEx(Graphics g)
{
oGraphics = g;
}
protected override IntPtr HookProc(IntPtr nColorDialogHandle, int msg, IntPtr wparam, IntPtr lparam)
{
IntPtr returnValue = base.HookProc(nColorDialogHandle, msg, wparam, lparam);
if (msg == WM_INITDIALOG)
{
IntPtr[] oStaticHandleArray = new IntPtr[9];
// Change the window title
SetWindowText(nColorDialogHandle, sColorPickerText);
// Get titlebar info for calculations later
TITLEBARINFO oTITLEBARINFO = new TITLEBARINFO();
oTITLEBARINFO.cbSize = (uint)System.Runtime.InteropServices.Marshal.SizeOf(oTITLEBARINFO);
GetTitleBarInfo(nColorDialogHandle, ref oTITLEBARINFO);
// Change the text of the "Basic colors:" label
oStaticHandleArray[0] = GetDlgItem(nColorDialogHandle, 0xFFFF);
SetWindowText(oStaticHandleArray[0], sBasicColorsText);
// Change the text of the "Define Custom Colors >>" button
SetWindowText(GetDlgItem(nColorDialogHandle, 0x2CF), sDefineCustomColorsButtonText);
// Save the "OK" button size and new width
Rectangle oOKButtonRect = new Rectangle();
int nOKButtonWidth = (int)oGraphics.MeasureString(sOKButtonText, new Font("Microsoft Sans Serif", 8, FontStyle.Regular)).Width + 20; // +20 accounts for extra +10 padding on either side
// Find the "OK" Button
IntPtr nChildHandle = GetDlgItem(nColorDialogHandle, 0x1);
if (nChildHandle.ToInt32() > 0)
{
// The "OK" button was found
// Now save the current size and position
GetWindowRect(nChildHandle.ToInt32(), ref oOKButtonRect);
// We have to subtract oOKButtonRect.X value from oOKButtonRect.Width to obtain the "real" button width (same thing with subtracting Y value from Height)
oOKButtonRect.Width = oOKButtonRect.Width - oOKButtonRect.X;
oOKButtonRect.Height = oOKButtonRect.Height - oOKButtonRect.Y;
// Resize the "OK" button so that the new text fits properly
// NOTE: I cannot be sure 100% if it is correct to use the titlebar to find the position of the button or not but the math works out in all of my tests
MoveWindow(nChildHandle, oOKButtonRect.X - oTITLEBARINFO.rcTitleBar.X, oOKButtonRect.Y - oTITLEBARINFO.rcTitleBar.Y - oTITLEBARINFO.rcTitleBar.Height, nOKButtonWidth, oOKButtonRect.Height, true);
// Finally, change the button text
SetWindowText(nChildHandle, sOKButtonText);
}
// Find the "Cancel" Button
nChildHandle = GetDlgItem(nColorDialogHandle, 0x2);
if (nChildHandle.ToInt32() > 0)
{
// The "Cancel" button was found
// Now get the current size and position
Rectangle oCancelButtonRect = new Rectangle();
GetWindowRect(nChildHandle.ToInt32(), ref oCancelButtonRect);
// We have to subtract oCancelButtonRect.X value from oCancelButtonRect.Width to obtain the "real" button width (same thing with subtracting Y value from Height)
oCancelButtonRect.Width = oCancelButtonRect.Width - oCancelButtonRect.X;
oCancelButtonRect.Height = oCancelButtonRect.Height - oCancelButtonRect.Y;
// Resize the "Cancel" button so that the new text fits properly
// NOTE: I cannot be sure 100% if it correct to use the titlebar to find the position of the button or not but the math works out in all of my tests
MoveWindow(nChildHandle, oOKButtonRect.X + nOKButtonWidth - oTITLEBARINFO.rcTitleBar.X + 6, oCancelButtonRect.Y - oTITLEBARINFO.rcTitleBar.Y - oTITLEBARINFO.rcTitleBar.Height, (int)oGraphics.MeasureString(sCancelButtonText, new Font("Microsoft Sans Serif", 8, FontStyle.Regular)).Width + 20, oCancelButtonRect.Height, true);
// Finally, change the button text
SetWindowText(nChildHandle, sCancelButtonText);
}
// Change the text of the "Add to Custom Colors" button
SetWindowText(GetDlgItem(nColorDialogHandle, 0x2C8), sAddToCustomColorsButtonText);
// Change the text of the "Color" label text
oStaticHandleArray[1] = GetDlgItem(nColorDialogHandle, 0x2DA);
SetWindowText(oStaticHandleArray[1], sColorText);
// Change the text of the "Solid" label text
oStaticHandleArray[2] = GetDlgItem(nColorDialogHandle, 0x2DB);
SetWindowText(oStaticHandleArray[2], sSolidText);
// Change the text of the "Hue:" label
oStaticHandleArray[3] = GetDlgItem(nColorDialogHandle, 0x2D3);
SetWindowText(oStaticHandleArray[3], sHueText);
// Change the text of the "Sat:" label
oStaticHandleArray[4] = GetDlgItem(nColorDialogHandle, 0x2D4);
SetWindowText(oStaticHandleArray[4], sSatText);
// Change the text of the "Lum:" label
oStaticHandleArray[5] = GetDlgItem(nColorDialogHandle, 0x2D5);
SetWindowText(oStaticHandleArray[5], sLumText);
// Change the text of the "Red:" label
oStaticHandleArray[6] = GetDlgItem(nColorDialogHandle, 0x2D6);
SetWindowText(oStaticHandleArray[6], sRedText);
// Change the text of the "Green:" label
oStaticHandleArray[7] = GetDlgItem(nColorDialogHandle, 0x2D7);
SetWindowText(oStaticHandleArray[7], sGreenText);
// Change the text of the "Blue:" label
oStaticHandleArray[8] = GetDlgItem(nColorDialogHandle, 0x2D8);
SetWindowText(oStaticHandleArray[8], sBlueText);
// Change the text of the "Custom Colors:" label
SetCustomColorsText(nColorDialogHandle, oStaticHandleArray);
}
return returnValue;
}
private static string GetClassName(IntPtr nHandle)
{
// Create the stringbuilder object that is used to get the window class name from the GetClassName win api function
System.Text.StringBuilder sClassName = new System.Text.StringBuilder(100);
GetClassName(nHandle, sClassName, sClassName.Capacity);
return sClassName.ToString();
}
private static string GetWindowText(IntPtr nHandle)
{
// Create the stringbuilder object that is used to get the window text from the GetWindowText win api function
System.Text.StringBuilder sWindowText = new System.Text.StringBuilder(100);
GetWindowText(nHandle, sWindowText, sWindowText.Capacity);
return sWindowText.ToString();
}
private void SetCustomColorsText(IntPtr nHandle, IntPtr[] oStaticHandleArray)
{
// Find the last control based on the handle to the main window
IntPtr nWorkingHandle = GetWindow(FindWindowEx(nHandle, IntPtr.Zero, null, null), GW_HWNDLAST);
bool bFound = false;
do
{
// Look only for "Static" controls that we have not already changed
if (GetClassName(nWorkingHandle) == "Static" && oStaticHandleArray.Contains(nWorkingHandle) == false)
{
// Found a "Static" control
// Check to see if it is the one we are looking for
string sControlText = GetWindowText(nWorkingHandle);
if (sControlText != "")
{
// Found the "Custom Colors:" label
// Change the text of the "Custom Colors:" label
SetWindowText(nWorkingHandle, sCustomColorsText);
bFound = true;
}
}
// Working backwards we look for the previous control
nWorkingHandle = GetWindow(nWorkingHandle, GW_HWNDPREV);
// Jump out of the loop when the working handle doesn't find anymore controls
if (nWorkingHandle == IntPtr.Zero)
break;
} while (bFound == false);
}
}
}
This dialog is already localized. You can look at it with Visual Studio. Copy c:\windows\system32\en-US\comdlg32.dll.mui to, say, c:\temp\test.dll. Replace "en-US" with your local language tag. In VS use File > Open > File and pick test.dll. You'll see the resources in the MUI file, open the Dialog node and double-click the one named "CHOOSECOLOR". The resource editor opens, you can pick an item in the dialog template and look at its properties in the Property window.
Hopefully it is obvious why the STATIC control has the default IDSTATIC id (65535), there is no need for Windows to do anything to change its properties so no need to find it back. And not for you either, your user will have his own copy of the MUI file that contains the dialog with strings in his native language.
Do note that a machine usually only has MUI files for a single language. If you need to do this to, say, create screenshots for documentation then start here.

Categories

Resources