Graphics.MeasureString() - Finding the relative X,Y coordinates - c#
I am attempting to create a custom ListBox control in WinForms.
I have subclassed it, set DrawMode.OwnerDrawFixed, and am overriding OnDrawItem. I have created a custom ColoredListBoxItem class that has additional properties to deal with the highlighting.
This is all fine. My issue is that this functionality needs to highlight words within the text of a list item.
Here is as far as I've gotten, and it doesn't work, because the X coordinate of the highlight remains constant and does not correspond to the actual X coordinate of the text.
How can I get a Point value (or Rectangle) to use with DrawText that will overlay the highlighted text? I've tried doing some math with the bounds of the original text Rectangle versus the highlight Rectangle but it is not working as expected.
protected override void OnDrawItem(DrawItemEventArgs e) {
ColoredListBoxItem item = this.Items[e.Index] as ColoredListBoxItem;
e.DrawBackground();
e.DrawFocusRectangle();
Rectangle fullMessageRect = e.Bounds;
// Draw the original, full text
TextRenderer.DrawText(e.Graphics, item.Message, e.Font,
new Point(fullMessageRect.X, fullMessageRect.Y),
this.ForeColor);
// Check if we have any text to be highlighted
if (SomethingToHighlight(item)) {
// Find the text to highlight, and get its area
SizeF highlightedAreaSize =
e.Graphics.MeasureString(item.TextToHightlight, e.Font);
PointF highlightAreaPoint = highlightedAreaSize.ToPointF();
Point point = new Point(Convert.ToInt32(highlightAreaPoint.X),
Convert.ToInt32(fullMessageRect.Y));
TextRenderer.DrawText(e.Graphics, item.TextToHightlight, e.Font,
point, this.ForeColor, item.HighlightColor);
}
}
Here is what I'm seeing in a demo app, where the output just shows work being done, and I am trying to highlight one particular word .. in this case "height".
Don't pay any attention to the actual output, it's a bunch of nonsense so I can see exactly how another part of the system is adjusting PictureBox images on the fly.
Lines that it thinks should be highlighted are shown twice, once in the original format and then again with the highlight applied. Notice how the highlighted part is correct in the Y coordinate, but does not change in the X.
Here's what I am seeing in the Watch window when I set a break point prior to writing the highlighted text:
Clearly, I don't need the variable highlightAreaPoint, because it's the same as highlightedAreaSize.
Probably something obvious here but I'm tired of fiddling with it at this point!
I can feel your pain as I have been there before. Actually, I wanted to design my own Textbox not inheriting from Microsoft.Textbox control and when I researched on-line, I sort of discouraged to learn 1000 reasons why one must not reinvent the wheel and why it is so difficult to do from scratch. Highlighting selection text was one of the major challenge among the others like right-to-left, caret positioning, non-fixed fonts etc. But I decided to fly against the wind because I had my reasons to do so and finally got what I wanted. Since my text selection code was for TextBox, I had to change it to suit your requirement as you are dealing with ListBox.
Following is the code snippet:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace HowToHighlightPortionOfText
{
public static class Helper
{
private static Rectangle dummy
{
get
{
return new Rectangle(0, 0, 10, 10);
}
}
const uint H = 0x00000000;
const uint V = 0x00000001;
const uint T = 0x00000002;
#region api functions
[DllImport("user32.dll")]
static extern int DrawText(IntPtr hdc, string lpStr, int nCount, ref Dimension lpRect, int wFormat);
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(this IntPtr hdc, IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern int DeleteObject(this IntPtr hObject);
[DllImport("gdi32.dll", EntryPoint = "GdiGradientFill", ExactSpelling = true)]
static extern bool GradientFill(IntPtr hdc, Trivertex[] pVertex,
uint dwNumVertex, uint[] pMesh, uint dwNumMesh, uint dwMode);
[DllImport("gdi32")]
public static extern int SetBkMode(this IntPtr hdc, int nBkMode);
[DllImport("gdi32.dll")]
public static extern uint SetTextColor(this IntPtr hdc, int crColor);
[DllImport("gdi32.dll")]
public static extern uint SetBkColor(this IntPtr hdc, int crColor);
#endregion
#region public methods
//use this function to hilight portion of listbox item text
public static void HilightItemText(this ListBox control, int itemIndex, int startIndex, int endIndex,
Color highlightForeColor, Color highlightBackColorStart, Color? highlightBackColorEnd = null)
{
var container = control.GetItemRectangle(itemIndex);
var text = control.GetItemText(itemIndex);
using (Graphics g = control.CreateGraphics())
{
g.HighlightText(control.Font, text, container, startIndex, endIndex,
highlightForeColor, highlightBackColorStart, highlightBackColorEnd);
}
}
public static void HighlightText(this IDeviceContext dc, Font font, string text,
Rectangle container, int start, int end, Color highlightForeColor, Color highlightBackColorStart,
Color? highlightBackColorEnd, DrawTextFlags? flags = null)
{
IntPtr hdc = dc.GetHdc();
IntPtr _font = SelectObject(hdc, font.ToHfont());
Dimension dm = container;
var flag = flags.getMeasureFlag(false);
SetBkMode(hdc, ColorTranslator.ToWin32(Color.Transparent));
//first draw whole text
DrawText(hdc, text, text.Length, ref dm, 0);
//now get the highlight rectangle which will draw the highlighted text
Rectangle textBound, uptoIndex;
var rect = hdc.rangeBound(text, container, start, end, out textBound, out uptoIndex, flags: flags);
dm = rect;
var _backColorEnd = highlightBackColorEnd ?? highlightBackColorStart;
hdc.Fill(rect, highlightBackColorStart, _backColorEnd, Angle.A0);
SetTextColor(hdc, ColorTranslator.ToWin32(highlightForeColor));
if (start < 0 || start > text.Length - 1 || end < 0 || end > text.Length - 1)
throw new Exception("start and end value must be with in text length");
var _text = text.Substring(start, end - start + 1);
DrawText(hdc, _text, _text.Length, ref dm, 0);
DeleteObject(SelectObject(hdc, _font));
dc.ReleaseHdc();
}
public static Rectangle RangeBound(this IDeviceContext dc, Font font, string text,
Rectangle container, int start, int end, DrawTextFlags? flags = null)
{
Rectangle textBound, uptoIndex;
return dc.RangeBound(font, text, container, start, end, out textBound, out uptoIndex, flags);
}
public static Rectangle GetPortionRectangleToHighlight(this ListBox control, int itemIndex, int startIndex, int endIndex)
{
var container = control.GetItemRectangle(itemIndex);
var text = control.GetItemText(itemIndex);
Rectangle rect;
using (Graphics g = control.CreateGraphics())
{
rect = g.RangeBound(control.Font, text, container, startIndex, endIndex);
}
return rect;
}
public static bool Fill(this IntPtr hdc, Rectangle rc, Color c1,
Color c2, Angle angle)
{
return hdc.Fill(rc.X, rc.Y, rc.Right, rc.Bottom, c1, c2, angle);
}
public static bool Fill(this IntPtr hdc, int x0, int y0, int x1, int y1, Color c1, Color c2, Angle angle)
{
Trivertex[] t = new Trivertex[4]
{
new Trivertex(x0, y0, c1),
new Trivertex(x1, y1, c2),
new Trivertex(x0, y1, c1, c2),
new Trivertex(x1, y0, c1, c2)
};
uint[] pMesh = new uint[] { 0, 1, 2, 0, 1, 3 };
switch ((int)angle % 180)
{
case 0:
return GradientFill(hdc, t, 2, pMesh, 1, H);
case 45:
return GradientFill(hdc, t, 4, pMesh, 2, T);
case 90:
return GradientFill(hdc, t, 2, pMesh, 1, V);
case 135:
t[0].x = x1;
t[3].x = x0;
t[1].x = x0;
t[2].x = x1;
return GradientFill(hdc, t, 4, pMesh, 2, T);
default:
return false;
}
}
#endregion
#region get the highlight rectangle
static Rectangle RangeBound(this IDeviceContext dc, Font font, string text,
Rectangle container, int start, int end, out Rectangle textBound, out Rectangle uptoIndex, DrawTextFlags? flags = null)
{
textBound = Rectangle.Empty;
uptoIndex = Rectangle.Empty;
if (string.IsNullOrEmpty(text)) return Rectangle.Empty;
IntPtr hdc = dc.GetHdc();
IntPtr _font = SelectObject(hdc, font.ToHfont());
var rc = hdc.rangeBound(text, container, start, end, out textBound, out uptoIndex, flags: flags);
DeleteObject(SelectObject(hdc, _font));
dc.ReleaseHdc();
return rc;
}
static TextMeasurement charRectangle(this IntPtr hdc, string text, Rectangle container,
string wholeText = null, Point? point = null, bool adjustByPoint = false, DrawTextFlags? flags = null)
{
if (string.IsNullOrEmpty(text)) return TextMeasurement.Default;
TextMeasurement measurement = new TextMeasurement();
Rectangle textBound;
wholeText = (wholeText ?? text);
var location = container.Location;
var measureWholeText = point == null;
measurement.UserPoint = point ?? Point.Empty;
textBound = hdc.textBound(wholeText, container, flags: flags);
var rect = textBound;
var p = point ?? new Point(container.Right, container.Y);
if (!measureWholeText)
{
if (p.X > textBound.Right)
p.X = textBound.Right;
else if (p.X < textBound.Left)
p.X = textBound.X;
}
var charIndex = 0;
var result = hdc.charRectangle(text, ref p, rect, flags, measureWholeText);
charIndex = Math.Max(0, result.Item2);
var rectangles = result.Item1;
measurement.Bounds = rectangles[0];
measurement.TextBounds = (measureWholeText) ? rectangles[1] : textBound;
rectangles[1] = measurement.TextBounds;
if (!measureWholeText && adjustByPoint && charIndex > 0)
{
float middle = (float)measurement.Bounds.Left +
measurement.Bounds.Width / 2;
if (p.X > middle - 1)
{
Rectangle r;
Dimension r1 = measurement.TextBounds;
var newresult = hdc.charBound(text, charIndex + 2, ref r1,
(int)flags.getMeasureFlag(false), out r);
if (!newresult.Equals(measurement.Bounds) &&
newresult.X > measurement.Bounds.X)
{
charIndex++;
measurement.Bounds = newresult;
}
}
}
if (measurement.Bounds.Size.Width<=0)
measurement.Bounds = new Rectangle(measurement.Bounds.Location, new Size(2, 2));
measurement.CharIndex = charIndex;
measurement.Char = '\0';
measurement.Char = text[Math.Min(charIndex, text.Length - 1)];
return measurement;
}
static Tuple<Rectangle[], int> charRectangle(this IntPtr hdc, string text, ref Point p, Rectangle rect,
DrawTextFlags? flags, bool measureWholeText = false)
{
int i = 0;
int middle = text.Length / 2, start = 0;
bool first = true;
do
{
var upto = hdc.Measure(text.Substring(0, middle), null, rect, flags);
bool found = upto.Has(p);
if (!found)
{
start = middle;
middle += (text.Length - middle) / 2;
first = false;
if (start == middle) break;
}
else break;
} while (middle > 1 && text.Length - middle > 1);
if (first)
{
return hdc.charRectangle(text.Substring(0, middle),
ref p, rect, flags);
}
else
{
Rectangle[] list = new Rectangle[2];
for (i = start; i <= middle; i++)
{
if (hdc.Measure(text, out list, p, i + 1, rect, flags))
break;
}
i = Math.Max(i, 0);
return new Tuple<Rectangle[], int>(list, i);
}
}
static Rectangle charBound(this IntPtr hdc, string text, int len,
ref Dimension bounds, int flag, out Rectangle whole)
{
DrawText(hdc, text, len, ref bounds, flag);
whole = bounds;
var rc = bounds;
if (len - 1 > 0 && len <= text.Length)
{
DrawText(hdc, text.Substring(0, len - 1), len - 1, ref rc, flag);
rc = Rectangle.FromLTRB(rc.Right, bounds.Top, bounds.Right, bounds.Bottom);
}
return rc;
}
static Rectangle rangeBound(this IntPtr hdc, string text, Rectangle container, int start, int end,
out Rectangle textBound, out Rectangle uptoIndex, DrawTextFlags? flags = null)
{
textBound = Rectangle.Empty;
uptoIndex = Rectangle.Empty;
if (string.IsNullOrEmpty(text)) return Rectangle.Empty;
var location = container.Location;
textBound = hdc.textBound(text, container, flags);
Dimension rect = textBound;
var flag = flags.getMeasureFlag(false);
start++;
var text1 = text.Substring(0, start);
var rc = hdc.charBound(text1, text1.Length, ref rect, (int)flag, out uptoIndex);
end++;
var text2 = text.Substring(0, end);
DrawText(hdc, text2, text2.Length, ref rect, (int)flag);
return Rectangle.FromLTRB(rc.Left, rect.Top, rect.Right, rect.Bottom);
}
static Rectangle textBound(this IntPtr hdc, string text, Rectangle container, DrawTextFlags? flags = null)
{
Rectangle rc = Rectangle.Empty;
if (string.IsNullOrEmpty(text)) return rc;
Point p = container.Location;
var r = hdc.Measure(text, text.Length, flags: flags);
return new Rectangle(p, r.Size);
}
static DrawTextFlags getMeasureFlag(this DrawTextFlags? flags, bool textboxControl = false)
{
DrawTextFlags flag = DrawTextFlags.CalculateArea;
if (flags != null) flag |= flags.Value;
flag |= DrawTextFlags.WordBreak | DrawTextFlags.NoPrefix
| DrawTextFlags.NoPadding | DrawTextFlags.NoClipping;
if (textboxControl) flag |= DrawTextFlags.TextBoxControl;
else flag |= DrawTextFlags.SingleLine;
return flag;
}
static Rectangle RangeBound(this IntPtr hdc, string text,
Rectangle container, int start, int end, DrawTextFlags? flags = null)
{
Rectangle textBound, uptoIndex;
return hdc.rangeBound(text, container, start, end, out textBound, out uptoIndex, flags);
}
static Rectangle Measure(this IntPtr hdc, string text, int? length = null,
Rectangle? rect = null, DrawTextFlags? flags = null)
{
if (string.IsNullOrEmpty(text)) return Rectangle.Empty;
Dimension bounds = rect ?? dummy;
var len = length ?? text.Length;
var flag = flags.getMeasureFlag(false);
var i = DrawText(hdc, text, len, ref bounds, (int)flag);
return bounds;
}
static bool Measure(this IntPtr hdc, string text, out Rectangle[] rectangles, Point p,
int? length = null, Rectangle? rect = null, DrawTextFlags? flags = null)
{
rectangles = new Rectangle[2];
if (string.IsNullOrEmpty(text)) return true;
Dimension bounds = rect ?? dummy;
var len = length ?? text.Length;
var flag = flags.getMeasureFlag(false);
Rectangle rc, rc1;
rc1 = hdc.charBound(text, len, ref bounds, (int)flag, out rc);
rectangles = new Rectangle[] { rc1, rc };
return (rectangles[0].Left < bounds.Left || rectangles[0].Has(p.X));
}
static bool Has(this Rectangle rect, int x = -1,
int y = -1, int checkRightUpto = -1, int checkBottomUpto = -1)
{
if (x == -1 && y == -1)
{
x = 0;
y = 0;
}
else
{
x = x == -1 ? rect.X : x;
y = y == -1 ? rect.Y : y;
}
if (checkRightUpto == -1)
{
checkRightUpto = rect.Width;
}
if (checkBottomUpto == -1)
{
checkBottomUpto = rect.Height;
}
return x >= rect.Left && x <= rect.Left +
checkRightUpto && y >= rect.Top &&
y <= rect.Top + checkBottomUpto;
}
static bool Has(this Rectangle rect, Point p,
int checkRightUpto = -1, int checkBottomUpto = -1)
{
return rect.Has(p.X, p.Y, checkRightUpto, checkBottomUpto);
}
#endregion
}
#region structs
[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); }
}
}
[StructLayout(LayoutKind.Sequential)]
public struct Trivertex
{
public int x;
public int y;
public ushort Red;
public ushort Green;
public ushort Blue;
public ushort Alpha;
public Trivertex(int x, int y, Color color)
: this(x, y, color.R, color.G, color.B, color.A)
{
}
public Trivertex(int x, int y, Color color, Color other)
: this(x, y, color.R, color.G, color.B, color.A, other)
{
}
public Trivertex(int x, int y, ushort red, ushort green, ushort blue, ushort alpha)
{
this.x = x;
this.y = y;
Red = (ushort)(red << 8);
Green = (ushort)(green << 8);
Blue = (ushort)(blue << 8);
Alpha = (ushort)(alpha << 8);
}
public Trivertex(int x, int y, ushort red, ushort green, ushort blue, ushort alpha, Color other)
{
this.x = x;
this.y = y;
Red = (ushort)((red + other.R / 2) << 8);
Green = (ushort)((green + other.G / 2) << 8);
Blue = (ushort)((blue + other.B / 2) << 8);
Alpha = (ushort)((alpha + other.A / 2) << 8);
}
public static ushort R(Color c)
{
return (ushort)(c.R << 8);
}
public static ushort G(Color c)
{
return (ushort)(c.G << 8);
}
public static ushort B(Color c)
{
return (ushort)(c.B << 8);
}
public static ushort R(Color c, Color c1)
{
return (ushort)(((c.R + c1.R / 2)) << 8);
}
public static ushort G(Color c, Color c1)
{
return (ushort)(((c.G + c1.G / 2)) << 8);
}
public static ushort B(Color c, Color c1)
{
return (ushort)(((c.B + c1.B / 2)) << 8);
}
}
#endregion
#region textmeasurement interface + class
public interface ITextMeasurement : ICloneable
{
int CharIndex { get; set; }
int PreviousIndex { get; }
Rectangle Bounds { get; }
Rectangle TextBounds { get; }
char Char { get; }
Point UserPoint { get; }
void CopyFrom(ITextMeasurement other);
}
public class TextMeasurement : ITextMeasurement
{
Rectangle now, textBound;
public virtual Rectangle Bounds
{
get
{
return now;
}
set { now = value; }
}
public virtual Rectangle TextBounds
{
get
{
return textBound; ;
}
set { textBound = value; }
}
public virtual int CharIndex { get; set; }
public virtual int PreviousIndex { get; set; }
public virtual char Char { get; set; }
public Point UserPoint { get; set; }
public virtual void CopyFrom(ITextMeasurement tm)
{
PreviousIndex = tm.PreviousIndex;
CharIndex = tm.CharIndex;
Bounds = tm.Bounds;
Char = tm.Char;
TextBounds = tm.TextBounds;
UserPoint = tm.UserPoint;
if (UserPoint.IsEmpty) UserPoint = Bounds.Location;
}
public virtual object Clone()
{
var tm = new TextMeasurement();
tm.CopyFrom(this);
return tm;
}
protected virtual void ResetBounds(Point p)
{
ResetBounds(p.X, p.Y);
}
protected virtual void ResetBounds(int? lineX = null, int? lineY = null)
{
if (lineX.HasValue)
{
now.X = lineX.Value;
textBound.X = lineX.Value;
}
if (lineY.HasValue)
{
now.Y = lineY.Value;
textBound.Y = lineY.Value;
}
}
protected virtual void ResetEmptyBounds(Rectangle rc)
{
now = rc;
textBound = rc;
}
public static TextMeasurement Default
{
get { return new TextMeasurement(); }
}
}
#endregion
#region enums
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,
}
public enum Angle
{
A0 = 0,
A45 = 45,
A90 = 90,
A135 = 135,
A180 = 180
}
#endregion
}
Suppose your ItemText at index 2 is "StackOverFlow is a wonderful site" and you want to highlight "StackOverFlow" then your startIndex =0 and endIndex = 12.
To highlight portion of text use HighlightItemText method:
listBox.HilightItemText(2, 0, 12, Color.Black, Color.Gold, Color.Yellow);
To get highlighted coordinates use GetPortionRectangleToHighlight method to get co-ordinates of text portion to highlight. Please note that you just need to pass start and end index as well of portion text.
so call the function like:
var portionRectangle = listBox1.GetPortionRectangleToHighlight (2, 0, 12);
Have a look at the attached image as working proof of concept.
A simple example would be something like this:
private string[] _sentences = {
"Old height on pictureOne: 766",
"New height on pictureOne: 900",
"",
"Forcing width on objectX"
};
private void Form1Paint(object sender, PaintEventArgs e) {
int y = 10; //Start position
int x;
foreach (string s in _sentences) {
x = 0; //Start position
foreach (string word in s.Split(' ')) {
if (ShouldHeighlightWord(word)) {
e.Graphics.DrawString(word + " ", this.Font, new SolidBrush(Color.Red), x, y);
}
else {
e.Graphics.DrawString(word + " ", this.Font, new SolidBrush(Color.Black), x, y);
}
x += (int)e.Graphics.MeasureString(word + " ", this.Font).Width;
}
y += (int)Math.Ceiling(e.Graphics.MeasureString("I", this.Font).Height);
}
}
private bool ShouldHeighlightWord(string word) {
switch (word) {
case "on":
case "Old":
return true;
default:
return false;
}
}
This code is just drawing the strings onto an empty form and instead of highlighting it just changes the color to Red.
But i think you understand what i mean.
Since i dont have more code its hard to make a better example for you.
When you check:
if (SomethingToHighlight(item)) {
That only returns true/false i guess and you need something that returns all words to be highlighted, but since a word can occur twice (or more) in one sentence you need to be able to get a position in the string as well. Or just take one word at a time and check if it should be highlighted or not and then draw it to the control.
Related
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
Bitmap gets mangled when I converted from System.Drawing.Bitmap object to cv::Mat
I have a WPF application that takes a screen shot of the running Handbrake executable using a class called ScreenCapture that I copied from stack overflow. public class ScreenCapture { [DllImport("user32.dll")] static extern int GetWindowRgn(IntPtr hWnd, IntPtr hRgn); //Region Flags - The return value specifies the type of the region that the function obtains. It can be one of the following values. const int ERROR = 0; const int NULLREGION = 1; const int SIMPLEREGION = 2; const int COMPLEXREGION = 3; [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect); [DllImport("gdi32.dll")] static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags); [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 Bitmap GetScreenshot(IntPtr ihandle) { IntPtr hwnd = ihandle;//handle here RECT rc; GetWindowRect(new HandleRef(null, hwnd), out rc); Bitmap bmp = new Bitmap(rc.Right - rc.Left, rc.Bottom - rc.Top, PixelFormat.Format32bppArgb); Graphics gfxBmp = Graphics.FromImage(bmp); IntPtr hdcBitmap; try { hdcBitmap = gfxBmp.GetHdc(); } catch { return null; } bool succeeded = PrintWindow(hwnd, hdcBitmap, 0); gfxBmp.ReleaseHdc(hdcBitmap); if (!succeeded) { gfxBmp.FillRectangle(new SolidBrush(Color.Gray), new Rectangle(Point.Empty, bmp.Size)); } IntPtr hRgn = CreateRectRgn(0, 0, 0, 0); GetWindowRgn(hwnd, hRgn); Region region = Region.FromHrgn(hRgn);//err here once if (!region.IsEmpty(gfxBmp)) { gfxBmp.ExcludeClip(region); gfxBmp.Clear(Color.Transparent); } gfxBmp.Dispose(); return bmp; } public void WriteBitmapToFile(string filename, Bitmap bitmap) { bitmap.Save(filename, ImageFormat.Bmp); } So when the button click handler below is called a screenshot of the handbrake window is taken. I write it to the harddrive to make sure its ok: handbrake screen shot. I create an instance of a CLR class library ClassLibrary1::Class1 and call the method "DoSomething" passing it the System.Drawing.Bitmap object. private void button4_Click(object sender, RoutedEventArgs e) { string wName = "HandBrake"; IntPtr hWnd = IntPtr.Zero; foreach (Process pList in Process.GetProcesses()) { if (pList.MainWindowTitle.Contains(wName)) { hWnd = pList.MainWindowHandle; var sc = new ScreenCapture(); SetForegroundWindow(hWnd); var bitmap = sc.GetScreenshot(hWnd); sc.WriteBitmapToFile("handbrake.bmp", bitmap); Bitmap image1 = (Bitmap)System.Drawing.Image.FromFile("handbrake.bmp", true); ClassLibrary1.Class1 opencv = new ClassLibrary1.Class1(); opencv.DoSomething(image1); } } } Inside DoSomething I attempt to convert the System.Drawing.Bitmap to a OpenCV class cv::Mat. I call cv::imwrite to make sure the bitmap is still ok, unfortunately somethings gone wrong: mangled handbrake screenshot void Class1::DoSomething(Bitmap ^mybitmap) { cv::Mat *imgOriginal; // Lock the bitmap's bits. Rectangle rect = Rectangle(0, 0, mybitmap->Width, mybitmap->Height); Imaging::BitmapData^ bmpData = mybitmap->LockBits(rect, Imaging::ImageLockMode::ReadWrite, mybitmap->PixelFormat); try { // Get the address of the first line. IntPtr ptr = bmpData->Scan0; // Declare an array to hold the bytes of the bitmap. // This code is specific to a bitmap with 24 bits per pixels. int bytes = Math::Abs(bmpData->Stride) * mybitmap->Height; array<Byte>^rgbValues = gcnew array<Byte>(bytes); // Copy the RGB values into the array. System::Runtime::InteropServices::Marshal::Copy(ptr, rgbValues, 0, bytes); imgOriginal = new cv::Mat(mybitmap->Height, mybitmap->Width, CV_8UC3, (void *)ptr, std::abs(bmpData->Stride)); } finally { mybitmap->UnlockBits(bmpData); }//Remember to unlock!!! cv::imwrite("from_mat.bmp", *imgOriginal); } Can anybody spot my error?
Since your image is stretched horizontally, I'm betting that you have the wrong pixel format selected. (It's not stretched vertically, nor skewed diagonally, so the stride is correct.) CV_8UC3 specifies 24 bits per pixel, but I think that your BMP file is using 32 bits per pixel. Switch your pixel format to CV_8UC4, or better yet, read the number of bits per pixel from the image and select the correct CV format based on that. Side note: Since you're doing sc.WriteBitmapToFile() followed by opencv.DoSomething(Image.FromFile(), the entire bit about how you're capturing the screenshot is irrelevant. You're reading the bitmap from a file; that's all that matters.
C# Quickest Way to Get Average Colors of Screen
I'm currently working on creating an Ambilight for my computer monitor with C#, an arduino, and an Ikea Dioder. Currently the hardware portion runs flawlessly; however, I'm having a problem with detecting the average color of a section of screen. I have two issues with the implementations that I'm using: Performance - Both of these algorithms add a somewhat noticeable stutter to the screen. Nothing showstopping, but it's annoying while watching video. No Fullscreen Game Support - When a game is in fullscreen mode both of these methods just return white. public class DirectxColorProvider : IColorProvider { private static Device d; private static Collection<long> colorPoints; public DirectxColorProvider() { PresentParameters present_params = new PresentParameters(); if (d == null) { d = new Device(new Direct3D(), 0, DeviceType.Hardware, IntPtr.Zero, CreateFlags.SoftwareVertexProcessing, present_params); } if (colorPoints == null) { colorPoints = GetColorPoints(); } } public byte[] GetColors() { var color = new byte[4]; using (var screen = this.CaptureScreen()) { DataRectangle dr = screen.LockRectangle(LockFlags.None); using (var gs = dr.Data) { color = avcs(gs, colorPoints); } } return color; } private Surface CaptureScreen() { Surface s = Surface.CreateOffscreenPlain(d, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, Format.A8R8G8B8, Pool.Scratch); d.GetFrontBufferData(0, s); return s; } private static byte[] avcs(DataStream gs, Collection<long> positions) { byte[] bu = new byte[4]; int r = 0; int g = 0; int b = 0; int i = 0; foreach (long pos in positions) { gs.Position = pos; gs.Read(bu, 0, 4); r += bu[2]; g += bu[1]; b += bu[0]; i++; } byte[] result = new byte[3]; result[0] = (byte)(r / i); result[1] = (byte)(g / i); result[2] = (byte)(b / i); return result; } private Collection<long> GetColorPoints() { const long offset = 20; const long Bpp = 4; var box = GetBox(); var colorPoints = new Collection<long>(); for (var x = box.X; x < (box.X + box.Length); x += offset) { for (var y = box.Y; y < (box.Y + box.Height); y += offset) { long pos = (y * Screen.PrimaryScreen.Bounds.Width + x) * Bpp; colorPoints.Add(pos); } } return colorPoints; } private ScreenBox GetBox() { var box = new ScreenBox(); int m = 8; box.X = (Screen.PrimaryScreen.Bounds.Width - m) / 3; box.Y = (Screen.PrimaryScreen.Bounds.Height - m) / 3; box.Length = box.X * 2; box.Height = box.Y * 2; return box; } private class ScreenBox { public long X { get; set; } public long Y { get; set; } public long Length { get; set; } public long Height { get; set; } } } You can find the file for the directX implmentation here. public class GDIColorProvider : Form, IColorProvider { private static Rectangle box; private readonly IColorHelper _colorHelper; public GDIColorProvider() { _colorHelper = new ColorHelper(); box = _colorHelper.GetCenterBox(); } public byte[] GetColors() { var colors = new byte[3]; IntPtr hDesk = GetDesktopWindow(); IntPtr hSrce = GetDC(IntPtr.Zero); IntPtr hDest = CreateCompatibleDC(hSrce); IntPtr hBmp = CreateCompatibleBitmap(hSrce, box.Width, box.Height); IntPtr hOldBmp = SelectObject(hDest, hBmp); bool b = BitBlt(hDest, box.X, box.Y, (box.Width - box.X), (box.Height - box.Y), hSrce, 0, 0, CopyPixelOperation.SourceCopy); using(var bmp = Bitmap.FromHbitmap(hBmp)) { colors = _colorHelper.AverageColors(bmp); } SelectObject(hDest, hOldBmp); DeleteObject(hBmp); DeleteDC(hDest); ReleaseDC(hDesk, hSrce); return colors; } // P/Invoke declarations [DllImport("gdi32.dll")] static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, CopyPixelOperation rop); [DllImport("user32.dll")] static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc); [DllImport("gdi32.dll")] static extern IntPtr DeleteDC(IntPtr hDc); [DllImport("gdi32.dll")] static extern IntPtr DeleteObject(IntPtr hDc); [DllImport("gdi32.dll")] static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight); [DllImport("gdi32.dll")] static extern IntPtr CreateCompatibleDC(IntPtr hdc); [DllImport("gdi32.dll")] static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp); [DllImport("user32.dll")] private static extern IntPtr GetDesktopWindow(); [DllImport("user32.dll")] private static extern IntPtr GetWindowDC(IntPtr ptr); [DllImport("user32.dll")] private static extern IntPtr GetDC(IntPtr ptr); } You Can Find the File for the GDI implementation Here. The Full Codebase Can be Found Here.
Updated Answer The problem of slow screen capture performance most likely is caused by BitBlt() doing a pixel conversion when the pixel formats of source and destination don't match. From the docs: If the color formats of the source and destination device contexts do not match, the BitBlt function converts the source color format to match the destination format. This is what caused slow performance in my code, especially in higher resolutions. The default pixel format seems to be PixelFormat.Format32bppArgb, so that's what you should use for the buffer: var screen = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb); var gfx = Graphics.FromImage(screen); gfx.CopyFromScreen(bounds.Location, new Point(0, 0), bounds.Size); The next source for slow performance is Bitmap.GetPixel() which does boundary checks. Never use it when analyzing every pixel. Instead lock the bitmap data and get a pointer to it: public unsafe Color GetAverageColor(Bitmap image, int sampleStep = 1) { var data = image.LockBits( new Rectangle(Point.Empty, Image.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); var row = (int*)data.Scan0.ToPointer(); var (sumR, sumG, sumB) = (0L, 0L, 0L); var stride = data.Stride / sizeof(int) * sampleStep; for (var y = 0; y < data.Height; y += sampleStep) { for (var x = 0; x < data.Width; x += sampleStep) { var argb = row[x]; sumR += (argb & 0x00FF0000) >> 16; sumG += (argb & 0x0000FF00) >> 8; sumB += argb & 0x000000FF; } row += stride; } image.UnlockBits(data); var numSamples = data.Width / sampleStep * data.Height / sampleStep; var avgR = sumR / numSamples; var avgG = sumG / numSamples; var avgB = sumB / numSamples; return Color.FromArgb((int)avgR, (int)avgG, (int)avgB); } This should get you well below 10 ms, depending on the screen size. In case it is still too slow you can increase the sampleStep parameter of GetAverageColor(). Original Answer I recently did the same thing and came up with something that worked surprisingly good. The trick is to create an additional bitmap that is 1x1 pixels in size and set a good interpolation mode on its graphics context (bilinear or bicubic, but not nearest neighbor). Then you draw your captured bitmap into that 1x1 bitmap exploiting the interpolation and retrieve that pixel to get the average color. I'm doing that at a rate of ~30 fps. When the screen shows a GPU rendering (e.g. watching YouTube full screen with enabled hardware acceleration in Chrome) there is no visible stuttering or anything. In fact, CPU utilization of the application is way below 10%. However, if I turn off Chrome's hardware acceleration then there is definitely some slight stuttering noticeable if you watch close enough. Here are the relevant parts of the code: using var screen = new Bitmap(width, height); using var screenGfx = Graphics.FromImage(screen); using var avg = new Bitmap(1, 1); using var avgGfx = Graphics.FromImage(avg); avgGfx.InterpolationMode = InterpolationMode.HighQualityBicubic; while (true) { screenGfx.CopyFromScreen(left, top, 0, 0, screen.Size); avgGfx.DrawImage(screen, 0, 0, avg.Width, avg.Height); var color = avg.GetPixel(0, 0); var bright = (int)Math.Round(Math.Clamp(color.GetBrightness() * 100, 1, 100)); // set color and brightness on your device // wait 1000/fps milliseconds } Note that this works for GPU renderings, because System.Drawing.Common uses GDI+ nowadays. However, it does not work when the content is DRM protected. So it won't work with Netflix for example :( I published the code on GitHub. Even though I abandoned the project due to Netflix' DRM protection it might help someone else.
Using an array of size 4 to represent a single point
I am working with Wiimote API and I came across this code, float[] srcX = new float[4]; float[] srcY = new float[4]; float[] dstX = new float[4]; float[] dstY = new float[4]; I am unable to understand why would there be an array of 4 floats to represent a single point. Please guide. Thanks. Here is the whole code, using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; using System.Text; using System.Windows.Forms; using System.Diagnostics; using System.Threading; using System.Runtime.InteropServices;//for firing keyboard and mouse events (optional) using System.IO;//for saving the reading the calibration data using WiimoteLib; namespace WiimoteWhiteboard { public partial class Form1 : Form { //instance of the wii remote Wiimote wm = new Wiimote(); const int smoothingBufferSize = 50; PointF[] smoothingBuffer = new PointF[smoothingBufferSize]; int smoothingBufferIndex = 0; int smoothingAmount = 4; bool enableSmoothing = true; bool cursorControl = false; int screenWidth = 1024;//defaults, gets replaced by actual screen size int screenHeight = 768; int calibrationState = 0; float calibrationMargin = .1f; CalibrationForm cf = null; Warper warper = new Warper(); float[] srcX = new float[4]; float[] srcY = new float[4]; float[] dstX = new float[4]; float[] dstY = new float[4]; //declare consts for mouse messages public const int INPUT_MOUSE = 0; public const int INPUT_KEYBOARD = 1; public const int INPUT_HARDWARE = 2; public const int MOUSEEVENTF_MOVE = 0x01; public const int MOUSEEVENTF_LEFTDOWN = 0x02; public const int MOUSEEVENTF_LEFTUP = 0x04; public const int MOUSEEVENTF_RIGHTDOWN = 0x08; public const int MOUSEEVENTF_RIGHTUP = 0x10; public const int MOUSEEVENTF_MIDDLEDOWN = 0x20; public const int MOUSEEVENTF_MIDDLEUP = 0x40; public const int MOUSEEVENTF_ABSOLUTE = 0x8000; //declare consts for key scan codes public const byte VK_TAB = 0x09; public const byte VK_MENU = 0x12; // VK_MENU is Microsoft talk for the ALT key public const byte VK_SPACE = 0x20; public const byte VK_RETURN = 0x0D; public const byte VK_LEFT =0x25; public const byte VK_UP =0x26; public const byte VK_RIGHT =0x27; public const byte VK_DOWN =0x28; public const int KEYEVENTF_EXTENDEDKEY = 0x01; public const int KEYEVENTF_KEYUP = 0x02; //for firing mouse and keyboard events [DllImport("user32.dll", SetLastError = true)] static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); [StructLayout(LayoutKind.Sequential)] public struct MOUSEINPUT { public int dx;//4 public int dy;//4 public uint mouseData;//4 public uint dwFlags;//4 public uint time;//4 public IntPtr dwExtraInfo;//4 } [StructLayout(LayoutKind.Sequential)] public struct KEYBDINPUT { public ushort wVk;//2 public ushort wScan;//2 public uint dwFlags;//4 public uint time;//4 public IntPtr dwExtraInfo;//4 } [StructLayout(LayoutKind.Sequential)] public struct HARDWAREINPUT { public uint uMsg; public ushort wParamL; public ushort wParamH; } [StructLayout(LayoutKind.Explicit, Size = 28)] public struct INPUT { [FieldOffset(0)] public int type; [FieldOffset(4)] //* public MOUSEINPUT mi; [FieldOffset(4)] //* public KEYBDINPUT ki; [FieldOffset(4)] //* public HARDWAREINPUT hi; } //imports mouse_event function from user32.dll [DllImport("user32.dll")] private static extern void mouse_event( long dwFlags, // motion and click options long dx, // horizontal position or change long dy, // vertical position or change long dwData, // wheel movement long dwExtraInfo // application-defined information ); [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetCursorPos(int X, int Y); //imports keybd_event function from user32.dll [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern void keybd_event(byte bVk, byte bScan, long dwFlags, long dwExtraInfo); WiimoteState lastWiiState = new WiimoteState();//helps with event firing //end keyboard and mouse input emulation variables---------------------------------------- Mutex mut = new Mutex(); public Form1() { screenWidth = Screen.GetBounds(this).Width; screenHeight = Screen.GetBounds(this).Height; InitializeComponent(); for (int i = 0; i < smoothingBufferSize; i++) smoothingBuffer[i] = new PointF(); setSmoothing(smoothingAmount); } private void Form1_Load(object sender, EventArgs e) { //add event listeners to changes in the wiiremote //fired for every input report - usually 100 times per second if acclerometer is enabled wm.WiimoteChanged += new WiimoteChangedEventHandler(wm_OnWiimoteChanged); //fired when the extension is attached on unplugged wm.WiimoteExtensionChanged += new WiimoteExtensionChangedEventHandler(wm_OnWiimoteExtensionChanged); try { //connect to wii remote wm.Connect(); //set what features you want to enable for the remote, look at Wiimote.InputReport for options wm.SetReportType(Wiimote.InputReport.IRAccel, true); //set wiiremote LEDs with this enumerated ID wm.SetLEDs(true, false, false, false); } catch (Exception x) { MessageBox.Show("Exception: " + x.Message); this.Close(); } loadCalibrationData(); } void wm_OnWiimoteExtensionChanged(object sender, WiimoteExtensionChangedEventArgs args) { //if extension attached, enable it if(args.Inserted) wm.SetReportType(Wiimote.InputReport.IRExtensionAccel, true); else wm.SetReportType(Wiimote.InputReport.IRAccel, true); } float UpdateTrackingUtilization() { //area of ideal calibration coordinates (to match the screen) float idealArea = (1 - 2*calibrationMargin) * 1024 * (1 - 2*calibrationMargin) * 768; //area of quadrliatera float actualArea = 0.5f * Math.Abs((srcX[1] - srcX[2]) * (srcY[0] - srcY[3]) - (srcX[0] - srcX[3]) * (srcY[1] - srcY[2])); float util = (actualArea / idealArea)*100; BeginInvoke((MethodInvoker)delegate() { lblTrackingUtil.Text = util.ToString("f0"); }); BeginInvoke((MethodInvoker)delegate() { pbTrackingUtil.Value = (int)util; }); return util; } PointF getSmoothedCursor(int amount) { int start = smoothingBufferIndex - amount; if (start < 0) start = 0; PointF smoothed = new PointF(0,0); int count = smoothingBufferIndex - start; for (int i = start; i < smoothingBufferIndex; i++) { smoothed.X += smoothingBuffer[i%smoothingBufferSize].X; smoothed.Y += smoothingBuffer[i % smoothingBufferSize].Y; } smoothed.X /= count; smoothed.Y /= count; return smoothed; } void wm_OnWiimoteChanged(object sender, WiimoteChangedEventArgs args) { mut.WaitOne(); //extract the wiimote state WiimoteState ws = args.WiimoteState; if (ws.IRState.Found1) { int x = ws.IRState.RawX1; int y = ws.IRState.RawY1; float warpedX = x; float warpedY = y; warper.warp(x, y, ref warpedX, ref warpedY); smoothingBuffer[smoothingBufferIndex % smoothingBufferSize].X = warpedX; smoothingBuffer[smoothingBufferIndex % smoothingBufferSize].Y = warpedY; smoothingBufferIndex++; if (!lastWiiState.IRState.Found1)//mouse down { lastWiiState.IRState.Found1 = ws.IRState.Found1; smoothingBufferIndex = 0;//resets the count if (cursorControl) { INPUT[] buffer = new INPUT[2]; buffer[0].type = INPUT_MOUSE; buffer[0].mi.dx = (int)(warpedX *65535.0f/screenWidth); buffer[0].mi.dy = (int)(warpedY * 65535.0f / screenHeight); buffer[0].mi.mouseData = 0; buffer[0].mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; buffer[0].mi.time = 0; buffer[0].mi.dwExtraInfo = (IntPtr)0; buffer[1].type = INPUT_MOUSE; buffer[1].mi.dx = 0; buffer[1].mi.dy = 0; buffer[1].mi.mouseData = 0; buffer[1].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; buffer[1].mi.time = 1; buffer[1].mi.dwExtraInfo = (IntPtr)0; SendInput(2, buffer, Marshal.SizeOf(buffer[0])); }//cusor control switch (calibrationState) { case 1: srcX[calibrationState - 1] = x; srcY[calibrationState - 1] = y; calibrationState = 2; doCalibration(); break; case 2: srcX[calibrationState - 1] = x; srcY[calibrationState - 1] = y; calibrationState = 3; doCalibration(); break; case 3: srcX[calibrationState - 1] = x; srcY[calibrationState - 1] = y; calibrationState = 4; doCalibration(); break; case 4: srcX[calibrationState - 1] = x; srcY[calibrationState - 1] = y; calibrationState = 5; doCalibration(); break; default: break; }//calibtation state }//mouse down else { if (cursorControl)//dragging { INPUT[] buffer = new INPUT[1]; buffer[0].type = INPUT_MOUSE; if (enableSmoothing) { PointF s = getSmoothedCursor(smoothingAmount); buffer[0].mi.dx = (int)(s.X * 65535.0f / screenWidth); buffer[0].mi.dy = (int)(s.Y * 65535.0f / screenHeight); } else { buffer[0].mi.dx = (int)(warpedX * 65535.0f / screenWidth); buffer[0].mi.dy = (int)(warpedY * 65535.0f / screenHeight); } buffer[0].mi.mouseData = 0; buffer[0].mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; buffer[0].mi.time = 0; buffer[0].mi.dwExtraInfo = (IntPtr)0; SendInput(1, buffer, Marshal.SizeOf(buffer[0])); } } }//ir visible else { if (lastWiiState.IRState.Found1)//mouse up { lastWiiState.IRState.Found1 = ws.IRState.Found1; if (cursorControl) { INPUT[] buffer = new INPUT[2]; buffer[0].type = INPUT_MOUSE; buffer[0].mi.dx = 0; buffer[0].mi.dy = 0; buffer[0].mi.mouseData = 0; buffer[0].mi.dwFlags = MOUSEEVENTF_LEFTUP; buffer[0].mi.time = 0; buffer[0].mi.dwExtraInfo = (IntPtr)0; buffer[1].type = INPUT_MOUSE; buffer[1].mi.dx = 0; buffer[1].mi.dy = 0; buffer[1].mi.mouseData = 0; buffer[1].mi.dwFlags = MOUSEEVENTF_MOVE; buffer[1].mi.time = 0; buffer[1].mi.dwExtraInfo = (IntPtr)0; SendInput(2, buffer, Marshal.SizeOf(buffer[0])); } }//ir lost } if (!lastWiiState.ButtonState.A && ws.ButtonState.A) { BeginInvoke((MethodInvoker)delegate() { btnCalibrate.PerformClick(); }); } lastWiiState.ButtonState.A = ws.ButtonState.A; if (!lastWiiState.ButtonState.B && ws.ButtonState.B) keybd_event(VK_SPACE, 0x45, 0, 0); if (lastWiiState.ButtonState.B && !ws.ButtonState.B) keybd_event(VK_SPACE, 0x45, KEYEVENTF_KEYUP, 0); lastWiiState.ButtonState.B = ws.ButtonState.B; if (!lastWiiState.ButtonState.Up && ws.ButtonState.Up) keybd_event(VK_UP, 0x45, 0, 0); if (lastWiiState.ButtonState.Up && !ws.ButtonState.Up) keybd_event(VK_UP, 0x45, KEYEVENTF_KEYUP, 0); lastWiiState.ButtonState.Up = ws.ButtonState.Up; if (!lastWiiState.ButtonState.Down && ws.ButtonState.Down) keybd_event(VK_DOWN, 0x45, 0, 0); if (lastWiiState.ButtonState.Down && !ws.ButtonState.Down) keybd_event(VK_DOWN, 0x45, KEYEVENTF_KEYUP, 0); lastWiiState.ButtonState.Down = ws.ButtonState.Down; if (!lastWiiState.ButtonState.Left && ws.ButtonState.Left) keybd_event(VK_LEFT, 0x45, 0, 0); if (lastWiiState.ButtonState.Left && !ws.ButtonState.Left) keybd_event(VK_LEFT, 0x45, KEYEVENTF_KEYUP, 0); lastWiiState.ButtonState.Left = ws.ButtonState.Left; if (!lastWiiState.ButtonState.Right && ws.ButtonState.Right) keybd_event(VK_RIGHT, 0x45, 0, 0); if (lastWiiState.ButtonState.Right && !ws.ButtonState.Right) keybd_event(VK_RIGHT, 0x45, KEYEVENTF_KEYUP, 0); lastWiiState.ButtonState.Right = ws.ButtonState.Right; lastWiiState.IRState.Found1 = ws.IRState.Found1; lastWiiState.IRState.RawX1 = ws.IRState.RawX1; lastWiiState.IRState.RawY1 = ws.IRState.RawY1; lastWiiState.IRState.Found2 = ws.IRState.Found2; lastWiiState.IRState.RawX2 = ws.IRState.RawX2; lastWiiState.IRState.RawY2 = ws.IRState.RawY2; lastWiiState.IRState.Found3 = ws.IRState.Found3; lastWiiState.IRState.RawX3 = ws.IRState.RawX3; lastWiiState.IRState.RawY3 = ws.IRState.RawY3; lastWiiState.IRState.Found4 = ws.IRState.Found4; lastWiiState.IRState.RawX4 = ws.IRState.RawX4; lastWiiState.IRState.RawY4 = ws.IRState.RawY4; //draw battery value on GUI //BeginInvoke((MethodInvoker)delegate() { pbBattery.Value = (ws.Battery > 0xc8 ? 0xc8 : (int)ws.Battery); }); //float f = (((100.0f * 48.0f * (float)(ws.Battery / 48.0f))) / 192.0f); //BeginInvoke((MethodInvoker)delegate() { lblBattery.Text = f.ToString("f0") + "%"; }); //check the GUI check boxes if the IR dots are visible //String irstatus = "Visible IR dots: "; //if (ws.IRState.Found1) // irstatus += "1 "; //if (ws.IRState.Found2) // irstatus += "2 "; //if (ws.IRState.Found3) // irstatus += "3 "; //if (ws.IRState.Found4) // irstatus += "4 "; //BeginInvoke((MethodInvoker)delegate() { lblIRvisible.Text = irstatus; }); mut.ReleaseMutex(); } public void loadCalibrationData() { // create reader & open file try { TextReader tr = new StreamReader("calibration.dat"); for (int i = 0; i < 4; i++) { srcX[i] = float.Parse(tr.ReadLine()); srcY[i] = float.Parse(tr.ReadLine()); } smoothingAmount = int.Parse(tr.ReadLine()); // close the stream tr.Close(); } catch (Exception x) { //no prexsting calibration return; } warper.setDestination( screenWidth * calibrationMargin, screenHeight * calibrationMargin, screenWidth * (1.0f-calibrationMargin), screenHeight * calibrationMargin, screenWidth * calibrationMargin, screenHeight * (1.0f - calibrationMargin), screenWidth * (1.0f - calibrationMargin), screenHeight * (1.0f - calibrationMargin)); warper.setSource(srcX[0], srcY[0], srcX[1], srcY[1], srcX[2], srcY[2], srcX[3], srcY[3]); warper.computeWarp(); setSmoothing(smoothingAmount); cursorControl = true; // BeginInvoke((MethodInvoker)delegate() { cbCursorControl.Checked = cursorControl; }); UpdateTrackingUtilization(); } public void saveCalibrationData() { TextWriter tw = new StreamWriter("calibration.dat"); // write a line of text to the file for (int i = 0; i < 4; i++) { tw.WriteLine(srcX[i]); tw.WriteLine(srcY[i]); } tw.WriteLine(smoothingAmount); // close the stream tw.Close(); } public void doCalibration(){ if (cf == null) return; int x = 0; int y = 0; int size = 25; Pen p = new Pen(Color.Red); switch (calibrationState) { case 1: x = (int)(screenWidth * calibrationMargin); y = (int)(screenHeight * calibrationMargin); cf.showCalibration(x, y, size, p); dstX[calibrationState - 1] = x; dstY[calibrationState - 1] = y; break; case 2: x = screenWidth - (int)(screenWidth * calibrationMargin); y = (int)(screenHeight * calibrationMargin); cf.showCalibration(x, y, size, p); dstX[calibrationState - 1] = x; dstY[calibrationState - 1] = y; break; case 3: x = (int)(screenWidth * calibrationMargin); y = screenHeight -(int)(screenHeight * calibrationMargin); cf.showCalibration(x, y, size, p); dstX[calibrationState - 1] = x; dstY[calibrationState - 1] = y; break; case 4: x = screenWidth - (int)(screenWidth * calibrationMargin); y = screenHeight -(int)(screenHeight * calibrationMargin); cf.showCalibration(x, y, size, p); dstX[calibrationState - 1] = x; dstY[calibrationState - 1] = y; break; case 5: //compute warp warper.setDestination(dstX[0], dstY[0], dstX[1], dstY[1], dstX[2], dstY[2], dstX[3], dstY[3]); warper.setSource(srcX[0], srcY[0], srcX[1], srcY[1], srcX[2], srcY[2], srcX[3], srcY[3]); warper.computeWarp(); cf.Close(); cf = null; calibrationState = 0; cursorControl = true; // BeginInvoke((MethodInvoker)delegate() { cbCursorControl.Checked = cursorControl; }); // saveCalibrationData(); UpdateTrackingUtilization(); break; default: break; } } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { //disconnect the wiimote wm.Disconnect(); saveCalibrationData(); } private void btnCalibrate_Click(object sender, EventArgs e) { if (cf == null) { cf = new CalibrationForm(); cf.Show(); } if (cf.IsDisposed) { cf = new CalibrationForm(); cf.Show(); } cursorControl = false; calibrationState = 1; doCalibration(); } private void cbCursorControl_CheckedChanged(object sender, EventArgs e) { //cursorControl = cbCursorControl.Checked; } private void label1_Click(object sender, EventArgs e) { } private void setSmoothing(int smoothing) { smoothingAmount = smoothing; //trackBar1.Value = smoothing; enableSmoothing = (smoothingAmount != 0); // lblSmoothing.Text = "Smoothing: " + smoothingAmount; } //private void trackBar1_Scroll(object sender, EventArgs e) //{ // smoothingAmount = trackBar1.Value; // enableSmoothing = (smoothingAmount != 0); // lblSmoothing.Text = "Smoothing: " + smoothingAmount; //} private void lblIRvisible_Click(object sender, EventArgs e) { } } }
It's used for calibration via matrix calculations. The Warper actually handles matrix transformations to calibrate the screen position etc I guess. The matrix is 4x4 --> 4 arrays with size of 4. Here you can have a look into the Code
How do I get the taskbar's position and size?
I want to know how to get the rectangle (bottom, top, left, and right) that the taskbar occupies. How do I go about doing this in C#?
private enum TaskBarLocation { TOP, BOTTOM, LEFT, RIGHT} private TaskBarLocation GetTaskBarLocation() { TaskBarLocation taskBarLocation = TaskBarLocation.BOTTOM; bool taskBarOnTopOrBottom = (Screen.PrimaryScreen.WorkingArea.Width == Screen.PrimaryScreen.Bounds.Width); if (taskBarOnTopOrBottom) { if (Screen.PrimaryScreen.WorkingArea.Top > 0) taskBarLocation = TaskBarLocation.TOP; } else { if (Screen.PrimaryScreen.WorkingArea.Left > 0) { taskBarLocation = TaskBarLocation.LEFT; } else { taskBarLocation = TaskBarLocation.RIGHT; } } return taskBarLocation; }
It's actually way more complicated than is shown above. For one thing, the task bar doesn't have to be on the primary screen, it can be dragged to any screen. For another, in theory there could be something docked on each edge of each given screen. The code above incorrectly assumes that finding something docked to one edge excludes all other edges. The only way the location of the task bar could be definitively derived from bounds vs workingarea, would be if only one edge out of all screens had something docked to it. The following function returns an array of Rectangles, each representing a docked task bar, and writes the count to its byref parameter. If that count is 1, element 0 of the returned array is the Rectangle occupied by the task bar. If greater than 1, time to guess? Public Function FindDockedTaskBars(ByRef DockedRectCounter As Integer) As Rectangle() Dim TmpScrn As Screen = Nothing Dim LeftDockedWidth As Integer = 0 Dim TopDockedHeight As Integer = 0 Dim RightDockedWidth As Integer = 0 Dim BottomDockedHeight As Integer = 0 Dim DockedRects(Screen.AllScreens.Count * 4) As Rectangle DockedRectCounter = 0 For Each TmpScrn In Screen.AllScreens If Not TmpScrn.Bounds.Equals(TmpScrn.WorkingArea) Then LeftDockedWidth = Math.Abs(Math.Abs(TmpScrn.Bounds.Left) - Math.Abs(TmpScrn.WorkingArea.Left)) TopDockedHeight = Math.Abs(Math.Abs(TmpScrn.Bounds.Top) - Math.Abs(TmpScrn.WorkingArea.Top)) RightDockedWidth = (TmpScrn.Bounds.Width - LeftDockedWidth) - TmpScrn.WorkingArea.Width BottomDockedHeight = (TmpScrn.Bounds.Height - TopDockedHeight) - TmpScrn.WorkingArea.Height If LeftDockedWidth > 0 Then DockedRects(DockedRectCounter).X = TmpScrn.Bounds.Left DockedRects(DockedRectCounter).Y = TmpScrn.Bounds.Top DockedRects(DockedRectCounter).Width = LeftDockedWidth DockedRects(DockedRectCounter).Height = TmpScrn.Bounds.Height DockedRectCounter += 1 End If If RightDockedWidth > 0 Then DockedRects(DockedRectCounter).X = TmpScrn.WorkingArea.Right DockedRects(DockedRectCounter).Y = TmpScrn.Bounds.Top DockedRects(DockedRectCounter).Width = RightDockedWidth DockedRects(DockedRectCounter).Height = TmpScrn.Bounds.Height DockedRectCounter += 1 End If If TopDockedHeight > 0 Then DockedRects(DockedRectCounter).X = TmpScrn.WorkingArea.Left DockedRects(DockedRectCounter).Y = TmpScrn.Bounds.Top DockedRects(DockedRectCounter).Width = TmpScrn.WorkingArea.Width DockedRects(DockedRectCounter).Height = TopDockedHeight DockedRectCounter += 1 End If If BottomDockedHeight > 0 Then DockedRects(DockedRectCounter).X = TmpScrn.WorkingArea.Left DockedRects(DockedRectCounter).Y = TmpScrn.WorkingArea.Bottom DockedRects(DockedRectCounter).Width = TmpScrn.WorkingArea.Width DockedRects(DockedRectCounter).Height = BottomDockedHeight DockedRectCounter += 1 End If End If Next Return DockedRects End Function Or for those of you who prefer C#... (Note: this ported code is untested) using System.Drawing; using System.Windows.Forms; public Rectangle[] FindDockedTaskBars(ref int DockedRectCounter) { int LeftDockedWidth = 0; int TopDockedHeight = 0; int RightDockedWidth = 0; int BottomDockedHeight = 0; Rectangle[] DockedRects = new Rectangle[Screen.AllScreens.Count() * 4]; DockedRectCounter = 0; foreach (Screen TmpScrn in Screen.AllScreens) { if (!TmpScrn.Bounds.Equals(TmpScrn.WorkingArea)) { LeftDockedWidth = Math.Abs(Math.Abs(TmpScrn.Bounds.Left) - Math.Abs(TmpScrn.WorkingArea.Left)); TopDockedHeight = Math.Abs(Math.Abs(TmpScrn.Bounds.Top) - Math.Abs(TmpScrn.WorkingArea.Top)); RightDockedWidth = (TmpScrn.Bounds.Width - LeftDockedWidth) - TmpScrn.WorkingArea.Width; BottomDockedHeight = (TmpScrn.Bounds.Height - TopDockedHeight) - TmpScrn.WorkingArea.Height; if (LeftDockedWidth > 0) { DockedRects[DockedRectCounter].X = TmpScrn.Bounds.Left; DockedRects[DockedRectCounter].Y = TmpScrn.Bounds.Top; DockedRects[DockedRectCounter].Width = LeftDockedWidth; DockedRects[DockedRectCounter].Height = TmpScrn.Bounds.Height; DockedRectCounter += 1; } if (RightDockedWidth > 0) { DockedRects[DockedRectCounter].X = TmpScrn.WorkingArea.Right; DockedRects[DockedRectCounter].Y = TmpScrn.Bounds.Top; DockedRects[DockedRectCounter].Width = RightDockedWidth; DockedRects[DockedRectCounter].Height = TmpScrn.Bounds.Height; DockedRectCounter += 1; } if (TopDockedHeight > 0) { DockedRects[DockedRectCounter].X = TmpScrn.WorkingArea.Left; DockedRects[DockedRectCounter].Y = TmpScrn.Bounds.Top; DockedRects[DockedRectCounter].Width = TmpScrn.WorkingArea.Width; DockedRects[DockedRectCounter].Height = TopDockedHeight; DockedRectCounter += 1; } if (BottomDockedHeight > 0) { DockedRects[DockedRectCounter].X = TmpScrn.WorkingArea.Left; DockedRects[DockedRectCounter].Y = TmpScrn.WorkingArea.Bottom; DockedRects[DockedRectCounter].Width = TmpScrn.WorkingArea.Width; DockedRects[DockedRectCounter].Height = BottomDockedHeight; DockedRectCounter += 1; } } } return DockedRects; }
Based on David's answer, here is a better implementation that uses P/Invoke to correctly determine the placement and size of the taskbar. The only limitation I know of so far is that it does not return the correct bounds when multiple monitors are set to display in extended mode. The code with all subsequent updates is available as a gist at https://git.io/v9bCx. /****************************************************************************** * Name: Taskbar.cs * Description: Class to get the taskbar's position, size and other properties. * Author: Franz Alex Gaisie-Essilfie * based on code from https://winsharp93.wordpress.com/2009/06/29/find-out-size-and-position-of-the-taskbar/ * * Change Log: * Date | Description * -------------|-------------------------------------------------------------- * 2017-05-16 | Initial design */ using System; using System.Drawing; using System.Runtime.InteropServices; namespace System.Windows.Forms { public enum TaskbarPosition { Unknown = -1, Left, Top, Right, Bottom, } public static class Taskbar { private enum ABS { AutoHide = 0x01, AlwaysOnTop = 0x02, } ////private enum ABE : uint private enum AppBarEdge : uint { Left = 0, Top = 1, Right = 2, Bottom = 3 } ////private enum ABM : uint private enum AppBarMessage : uint { New = 0x00000000, Remove = 0x00000001, QueryPos = 0x00000002, SetPos = 0x00000003, GetState = 0x00000004, GetTaskbarPos = 0x00000005, Activate = 0x00000006, GetAutoHideBar = 0x00000007, SetAutoHideBar = 0x00000008, WindowPosChanged = 0x00000009, SetState = 0x0000000A, } private const string ClassName = "Shell_TrayWnd"; private static APPBARDATA _appBarData; /// <summary>Static initializer of the <see cref="Taskbar" /> class.</summary> static Taskbar() { _appBarData = new APPBARDATA { cbSize = (uint)Marshal.SizeOf(typeof(APPBARDATA)), hWnd = FindWindow(Taskbar.ClassName, null) }; } /// <summary> /// Gets a value indicating whether the taskbar is always on top of other windows. /// </summary> /// <value><c>true</c> if the taskbar is always on top of other windows; otherwise, <c>false</c>.</value> /// <remarks>This property always returns <c>false</c> on Windows 7 and newer.</remarks> public static bool AlwaysOnTop { get { int state = SHAppBarMessage(AppBarMessage.GetState, ref _appBarData).ToInt32(); return ((ABS)state).HasFlag(ABS.AlwaysOnTop); } } /// <summary> /// Gets a value indicating whether the taskbar is automatically hidden when inactive. /// </summary> /// <value><c>true</c> if the taskbar is set to auto-hide is enabled; otherwise, <c>false</c>.</value> public static bool AutoHide { get { int state = SHAppBarMessage(AppBarMessage.GetState, ref _appBarData).ToInt32(); return ((ABS)state).HasFlag(ABS.AutoHide); } } /// <summary>Gets the current display bounds of the taskbar.</summary> public static Rectangle CurrentBounds { get { var rect = new RECT(); if (GetWindowRect(Handle, ref rect)) return Rectangle.FromLTRB(rect.Left, rect.Top, rect.Right, rect.Bottom); return Rectangle.Empty; } } /// <summary>Gets the display bounds when the taskbar is fully visible.</summary> public static Rectangle DisplayBounds { get { if (RefreshBoundsAndPosition()) return Rectangle.FromLTRB(_appBarData.rect.Left, _appBarData.rect.Top, _appBarData.rect.Right, _appBarData.rect.Bottom); return CurrentBounds; } } /// <summary>Gets the taskbar's window handle.</summary> public static IntPtr Handle { get { return _appBarData.hWnd; } } /// <summary>Gets the taskbar's position on the screen.</summary> public static TaskbarPosition Position { get { if (RefreshBoundsAndPosition()) return (TaskbarPosition)_appBarData.uEdge; return TaskbarPosition.Unknown; } } /// <summary>Hides the taskbar.</summary> public static void Hide() { const int SW_HIDE = 0; ShowWindow(Handle, SW_HIDE); } /// <summary>Shows the taskbar.</summary> public static void Show() { const int SW_SHOW = 1; ShowWindow(Handle, SW_SHOW); } private static bool RefreshBoundsAndPosition() { //! SHAppBarMessage returns IntPtr.Zero **if it fails** return SHAppBarMessage(AppBarMessage.GetTaskbarPos, ref _appBarData) != IntPtr.Zero; } #region DllImports [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect); [DllImport("shell32.dll", SetLastError = true)] private static extern IntPtr SHAppBarMessage(AppBarMessage dwMessage, [In] ref APPBARDATA pData); [DllImport("user32.dll")] private static extern int ShowWindow(IntPtr hwnd, int command); #endregion DllImports [StructLayout(LayoutKind.Sequential)] private struct APPBARDATA { public uint cbSize; public IntPtr hWnd; public uint uCallbackMessage; public AppBarEdge uEdge; public RECT rect; public int lParam; } [StructLayout(LayoutKind.Sequential)] private struct RECT { public int Left; public int Top; public int Right; public int Bottom; } } }
private enum TaskBarLocation { TOP, BOTTOM, LEFT, RIGHT } private TaskBarLocation GetTaskBarLocation() { //System.Windows.SystemParameters.... if (SystemParameters.WorkArea.Left > 0) return TaskBarLocation.LEFT; if (SystemParameters.WorkArea.Top > 0) return TaskBarLocation.TOP; if (SystemParameters.WorkArea.Left == 0 && SystemParameters.WorkArea.Width < SystemParameters.PrimaryScreenWidth) return TaskBarLocation.RIGHT; return TaskBarLocation.BOTTOM; }
This is the answer from Mark McGinty in C#. This code brings back all of the task bars as a list of rectanges: 0 rectangles means the taskbar is hidden; 1 rectangle is the position of the taskbar; 2+ is very rare, it means that we have multiple monitors, and we are not using Extend these displays to create a single virtual desktop. Works in every situation It works well with: Windows 7 (will almost certainly work on Windows 8.1 and Windows 10). All combinations of settings. C# Code public static List<Rectangle> FindDockedTaskBars() { List<Rectangle> dockedRects = new List<Rectangle>(); foreach (var tmpScrn in Screen.AllScreens) { if (!tmpScrn.Bounds.Equals(tmpScrn.WorkingArea)) { Rectangle rect = new Rectangle(); var leftDockedWidth = Math.Abs((Math.Abs(tmpScrn.Bounds.Left) - Math.Abs(tmpScrn.WorkingArea.Left))); var topDockedHeight = Math.Abs((Math.Abs(tmpScrn.Bounds.Top) - Math.Abs(tmpScrn.WorkingArea.Top))); var rightDockedWidth = ((tmpScrn.Bounds.Width - leftDockedWidth) - tmpScrn.WorkingArea.Width); var bottomDockedHeight = ((tmpScrn.Bounds.Height - topDockedHeight) - tmpScrn.WorkingArea.Height); if ((leftDockedWidth > 0)) { rect.X = tmpScrn.Bounds.Left; rect.Y = tmpScrn.Bounds.Top; rect.Width = leftDockedWidth; rect.Height = tmpScrn.Bounds.Height; } else if ((rightDockedWidth > 0)) { rect.X = tmpScrn.WorkingArea.Right; rect.Y = tmpScrn.Bounds.Top; rect.Width = rightDockedWidth; rect.Height = tmpScrn.Bounds.Height; } else if ((topDockedHeight > 0)) { rect.X = tmpScrn.WorkingArea.Left; rect.Y = tmpScrn.Bounds.Top; rect.Width = tmpScrn.WorkingArea.Width; rect.Height = topDockedHeight; } else if ((bottomDockedHeight > 0)) { rect.X = tmpScrn.WorkingArea.Left; rect.Y = tmpScrn.WorkingArea.Bottom; rect.Width = tmpScrn.WorkingArea.Width; rect.Height = bottomDockedHeight; } else { // Nothing found! } dockedRects.Add(rect); } } if (dockedRects.Count == 0) { // Taskbar is set to "Auto-Hide". } return dockedRects; }
This is a simple example using winforms with wpf and multi screen support: Screen sc = Screen.FromHandle(new WindowInteropHelper(this).Handle); if (sc.WorkingArea.Top > 0) { // TASKBAR TOP } else if (sc.WorkingArea.Left != sc.Bounds.X) { // TASKBAR LEFT } else if ((sc.Bounds.Height - sc.WorkingArea.Height) > 0) { // TASKBAR BOTTOM } else if (sc.WorkingArea.Right != 0) { // TASKBAR RIGHT } else { // TASKBAR NOT FOUND }
[StructLayout(LayoutKind.Sequential)] public struct RECT { public Int32 left; public Int32 top; public Int32 right; public Int32 bottom; } [StructLayout(LayoutKind.Sequential)] public struct APPBARDATA { public UInt32 cbSize; public IntPtr hWnd; public UInt32 uCallbackMessage; public UInt32 uEdge; public RECT rc; public IntPtr lParam; } [DllImport("shell32.dll")] public static extern IntPtr SHAppBarMessage(UInt32 dwMessage, ref APPBARDATA pData); private void Form1_Load(object sender, EventArgs e) { APPBARDATA msgData = new APPBARDATA(); msgData.cbSize = (UInt32)Marshal.SizeOf(msgData); // get taskbar position SHAppBarMessage((UInt32)0x00000005, ref msgData); RECT taskRect = msgData.rc; Console.WriteLine("top:" + taskRect.top + "; left:" + taskRect.left + "; bottom:" + taskRect.bottom + "; right:" + taskRect.right); Console.WriteLine("width:" + (taskRect.right - taskRect.left) + "; height:" + (taskRect.bottom - taskRect.top)); } Output: top:1040; left:0; bottom:1080; right:1920 width:1920; height:40
This is how to get the Taskbar's Height (using WPF) int PSBH = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height int TaskBarHeight = PSBH - System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height; If you want to account for dpi int PSH = SystemParameters.PrimaryScreenHeight; int PSBH = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height; double ratio = PSH / PSBH; int TaskBarHeight = PSBH - System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height; TaskBarHeight *= ratio;
I hate to say this, but one of the best and most consistent methods for setting your window size when using a WindowStyle of none is to create a temporary window that is created, maximized, Width and Height recorded, and then destroyed. private (double height, double width) GetVirtualWindowSize() { Window virtualWindow = new Window(); virtualWindow.Show(); virtualWindow.WindowState = WindowState.Maximized; double returnHeight = virtualWindow.Height; double returnWidth = virtualWindow.Width; virtualWindow.Close(); return (returnHeight, returnWidth); } You can then set the MaxHeight and MaxWidth properties of your window using the values returned within the tuple. This "sacrificial" window will use the standard window frame, and so Windows knows how to properly maximize it, thus returning accurate values every time. You can do this at startup so that you only have to deal with the window creation once, or you can recheck the dimensions on every maximize. I know it isn't pretty, and there is a brief flash. But it always works for me, no matter where the taskbar is, no matter the scaling, and no matter the changes that have taken place since the application started.
This will work on Windows and macOS. It will also work with multiple monitors. As is, it is for electron applications, but you can easily understand what's going on. type TaskBarPos int const ( UNKNOWN TaskBarPos = -1 LEFT TaskBarPos = 0 RIGHT TaskBarPos = 1 TOP TaskBarPos = 2 BOTTOM TaskBarPos = 3 ) type Rect struct { top int bottom int width int height int left int right int } func (r Rect) centerX() int { return r.left + ((r.right - r.left) / 2) } func (r Rect) centerY() int { return r.top + ((r.bottom - r.top) / 2) } func taskbar(tray *js.Object) TaskBarPos { // Step 1 - Get relevant display display := screen.Call("getDisplayNearestPoint", screen.Call("getCursorScreenPoint")) // Replace with primary monitor or a secondary monitor. This line as is grabs the monitor that the mouse cursor is on. // Step 2 - Determine taskbar bounds relative to the display bounds := display.Get("bounds") workArea := display.Get("workArea") var tb *Rect d := Rect{ top: bounds.Get("y").Int(), bottom: bounds.Get("y").Int() + bounds.Get("height").Int(), width: bounds.Get("width").Int(), height: bounds.Get("height").Int(), left: bounds.Get("x").Int(), right: bounds.Get("x").Int() + bounds.Get("width").Int(), } wa := Rect{ top: workArea.Get("y").Int(), bottom: workArea.Get("y").Int() + workArea.Get("height").Int(), width: workArea.Get("width").Int(), height: workArea.Get("height").Int(), left: workArea.Get("x").Int(), right: workArea.Get("x").Int() + workArea.Get("width").Int(), } if tray != nil { tBounds := tray.Call("getBounds") tb = &Rect{ top: tBounds.Get("y").Int(), bottom: tBounds.Get("y").Int() + tBounds.Get("height").Int(), width: tBounds.Get("width").Int(), height: tBounds.Get("height").Int(), left: tBounds.Get("x").Int(), right: tBounds.Get("x").Int() + tBounds.Get("width").Int(), } } // Step 3 - Determine Position of Taskbar if wa.top > d.top { return TOP } else if wa.bottom < d.bottom { return BOTTOM } else if wa.left > d.left { return LEFT } else if wa.right < d.right { return RIGHT } if tb == nil { return UNKNOWN } // Check which corner tray is closest to if ((*tb).top - d.top) < (d.bottom - (*tb).bottom) { return TOP } if ((*tb).left - d.left) < (d.right - (*tb).right) { return LEFT } if d.bottom-(*tb).centerY() < d.right-(*tb).centerX() { return BOTTOM } return RIGHT }