Goal is to create DateTimePicker similar to the screen shot of this question.
First attempt overriding OnPaint:
public class MyDateTimePicker : DateTimePicker
{
private Image _image;
public MyDateTimePicker() : base()
{
SetStyle(ControlStyles.UserPaint | ControlStyles.ResizeRedraw |
ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
}
[Browsable(true)]
public override Color BackColor
{
get
{
return base.BackColor;
}
set
{
base.BackColor = value;
}
}
[Category("Appearance")]
public Color BorderColor { get; set; } = Color.Black;
[Category("Appearance")]
public Color TextColor { get; set; } = Color.Black;
[Category("Appearance")]
public Image Image
{
get
{
return _image;
}
set
{
_image = value;
Invalidate();
}
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
// Fill the Background
e.Graphics.FillRectangle(new SolidBrush(this.BackColor), 0, 0, ClientRectangle.Width, ClientRectangle.Height);
// Draw DateTime text
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), 5, 2);
// Draw Icon
if (_image != null)
{
Rectangle im_rect = new Rectangle(ClientRectangle.Width - 20, 2, ClientRectangle.Height - 4, ClientRectangle.Height - 4);
e.Graphics.DrawImage(_image, im_rect);
}
// Draw Border
e.Graphics.DrawRectangle(Pens.Black, new Rectangle(0, 0, ClientRectangle.Width - 1, ClientRectangle.Height - 1));
}
}
This solution has the following issues: date fields are not clickable, text artifacts when changing date with arrow keys, narrow clickable area of the button.
Second solution overriding WndProc:
public class MyDateTimePicker : DateTimePicker
{
private const int WM_PAINT = 0x000F;
private Color _borderColor = Color.Black;
public MyDateTimePicker() { }
[Category("Appearance")]
public Color BorderColor
{
get { return _borderColor; }
set
{
if (_borderColor != value)
{
_borderColor = value;
this.Invalidate();
}
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_PAINT:
base.WndProc(ref m);
using (var g = Graphics.FromHwnd(m.HWnd))
{
var rect = new Rectangle(0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1);
g.DrawRectangle(new Pen(this.BorderColor), rect);
}
m.Result = IntPtr.Zero;
break;
default:
base.WndProc(ref m);
break;
}
}
}
This solution lacks the customization of the button. Maybe anyone knows how to customize button in this way, or how to solve issues of the first solution?
Also if it is possible I would like to change the height of DateTimePicker to match height of ComboBox (currently they differ by 1px).
You can handle WM_PAINT and draw the border and button yourself. To get the accurate size of the dropdown, send DTM_GETDATETIMEPICKERINFO message.
The width of the dropdown button may vary depending to the size of the control and the space required by the text of the control:
Flat DateTimePicker
using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class FlatDateTimePicker : DateTimePicker
{
public FlatDateTimePicker()
{
SetStyle(ControlStyles.ResizeRedraw |
ControlStyles.OptimizedDoubleBuffer, true);
}
private Color borderColor = Color.DeepSkyBlue;
[DefaultValue(typeof(Color), "RoyalBlue")]
public Color BorderColor
{
get { return borderColor; }
set
{
if (borderColor != value)
{
borderColor = value;
Invalidate();
}
}
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
var info = new DATETIMEPICKERINFO();
info.cbSize = Marshal.SizeOf(info);
SendMessage(Handle, DTM_GETDATETIMEPICKERINFO, IntPtr.Zero, ref info);
using (var g = Graphics.FromHwndInternal(Handle))
{
var clientRect = new Rectangle(0,0,Width, Height);
var buttonWidth = info.rcButton.R - info.rcButton.L;
var dropDownRect = new Rectangle(info.rcButton.L, info.rcButton.T,
buttonWidth, clientRect.Height);
if (RightToLeft == RightToLeft.Yes && RightToLeftLayout == true)
{
dropDownRect.X = clientRect.Width - dropDownRect.Right;
dropDownRect.Width += 1;
}
var middle = new Point(dropDownRect.Left + dropDownRect.Width / 2,
dropDownRect.Top + dropDownRect.Height / 2);
var arrow = new Point[]
{
new Point(middle.X - 3, middle.Y - 2),
new Point(middle.X + 4, middle.Y - 2),
new Point(middle.X, middle.Y + 2)
};
var borderAndButtonColor = Enabled ? BorderColor : Color.LightGray;
var arrorColor = BackColor;
using (var pen = new Pen(borderAndButtonColor))
g.DrawRectangle(pen, 0, 0,
clientRect.Width - 1, clientRect.Height - 1);
using (var brush = new SolidBrush(borderAndButtonColor))
g.FillRectangle(brush, dropDownRect);
g.FillPolygon(Brushes.Black, arrow);
}
}
}
const int WM_PAINT = 0xF;
const int DTM_FIRST = 0x1000;
const int DTM_GETDATETIMEPICKERINFO = DTM_FIRST + 14;
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, int Msg,
IntPtr wParam, ref DATETIMEPICKERINFO info);
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int L, T, R, B;
}
[StructLayout(LayoutKind.Sequential)]
struct DATETIMEPICKERINFO
{
public int cbSize;
public RECT rcCheck;
public int stateCheck;
public RECT rcButton;
public int stateButton;
public IntPtr hwndEdit;
public IntPtr hwndUD;
public IntPtr hwndDropDown;
}
}
Clone or Download Extended version
I have created an extended version of this answer, which supports rendering the up-down button and the checkbox in flat style, also highlighting the arrow on mouse move, something like this:
You can download or close the code:
r-aghaei/FlatDateTimePickerExample
master.zip
Related Posts
You may also want to take a look at the following flat style controls:
Flat TextBox - Change border color of TextBox
Flat ComboBox - Change border color and dropdown button of ComboBox
Flat NumericUpDown - Change border color and spin buttons of NumericUpDown
I know that there is a way how to set my application desktop toolbar to autohide, but unfortunately I don't know how to use it properly. Can you someone give me an example, please? I'm programming AppBar in C# WinForm.
Thank you very much
There is a code, which I'm using for registering the AppBar.
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[StructLayout(LayoutKind.Sequential)]
private struct APPBARDATA
{
public int cbSize;
public IntPtr hWnd;
public int uCallbackMessage;
public int uEdge;
public RECT rc;
public IntPtr lParam;
}
private enum ABMsg : int
{
ABM_NEW = 0,
ABM_REMOVE,
ABM_QUERYPOS,
ABM_SETPOS,
ABM_GETSTATE,
ABM_GETTASKBARPOS,
ABM_ACTIVATE,
ABM_GETAUTOHIDEBAR,
ABM_SETAUTOHIDEBAR,
ABM_WINDOWPOSCHANGED,
ABM_SETSTATE
}
private enum ABNotify : int
{
ABN_STATECHANGE = 0,
ABN_POSCHANGED,
ABN_FULLSCREENAPP,
ABN_WINDOWARRANGE
}
private enum ABEdge : int
{
ABE_LEFT = 0,
ABE_TOP,
ABE_RIGHT,
ABE_BOTTOM
}
private bool isBarRegistered = false;
private int uCallBack;
[DllImport("SHELL32", CallingConvention = CallingConvention.StdCall)]
private static extern uint SHAppBarMessage(int dwMessage, ref APPBARDATA pData);
[DllImport("USER32")]
private static extern int GetSystemMetrics(int Index);
[DllImport("User32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int cx, int cy, bool repaint);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
private static extern int RegisterWindowMessage(string msg);
private void RegisterBar(bool dvojty)
{
APPBARDATA abd = new APPBARDATA();
abd.cbSize = Marshal.SizeOf(abd);
abd.hWnd = this.Handle;
if (!isBarRegistered)
{
if (Properties.Settings.Default.Strana.ToLower() == "ano")
{
this.Width = minSirka;
this.Height = Screen.FromPoint(this.Location).WorkingArea.Height;
}
else
{
this.Width = Screen.FromPoint(this.Location).WorkingArea.Width;
this.Height = minVyska;
}
uCallBack = RegisterWindowMessage("AppBarMessage");
abd.uCallbackMessage = uCallBack;
uint ret = SHAppBarMessage((int)ABMsg.ABM_NEW, ref abd);
isBarRegistered = true;
ABSetPos(hrana);
}
else
{
toolBar = new Rectangle(0, 0, 0, 0);
SHAppBarMessage((int)ABMsg.ABM_REMOVE, ref abd);
isBarRegistered = false;
//this.TopMost = true;
if (dvojty)
{
if (Properties.Settings.Default.Strana.ToLower() == "ano")
{
this.Width = minSirka;
this.Height = Screen.FromPoint(this.Location).WorkingArea.Height;
}
else
{
this.Width = Screen.FromPoint(this.Location).WorkingArea.Width;
this.Height = minVyska;
}
uCallBack = RegisterWindowMessage("AppBarMessage");
abd.uCallbackMessage = uCallBack;
uint ret = SHAppBarMessage((int)ABMsg.ABM_NEW, ref abd);
isBarRegistered = true;
ABSetPos(hrana);
}
}
}
private void ABSetPos(string edge)
{
APPBARDATA abd = new APPBARDATA();
//SHAppBarMessage((int)ABMsg.ABM_SETAUTOHIDEBAR, ref abd);
abd.cbSize = Marshal.SizeOf(abd);
abd.hWnd = this.Handle;
if (edge == "" || edge == "top")
{
abd.uEdge = (int)ABEdge.ABE_TOP;
}
else if (edge == "right")
{
abd.uEdge = (int)ABEdge.ABE_RIGHT;
}
else if (edge == "left")
{
abd.uEdge = (int)ABEdge.ABE_LEFT;
}
else if (edge == "bottom")
{
abd.uEdge = (int)ABEdge.ABE_BOTTOM;
}
else
{
abd.uEdge = (int)ABEdge.ABE_TOP;
}
if (abd.uEdge == (int)ABEdge.ABE_LEFT || abd.uEdge == (int)ABEdge.ABE_RIGHT)
{
abd.rc.top = 0;
abd.rc.bottom = SystemInformation.PrimaryMonitorSize.Height;
if (abd.uEdge == (int)ABEdge.ABE_LEFT)
{
abd.rc.left = 0;
abd.rc.right = Size.Width;
okraj = "left";
}
else
{
abd.rc.right = SystemInformation.PrimaryMonitorSize.Width;
abd.rc.left = abd.rc.right - Size.Width;
okraj = "right";
}
}
else
{
abd.rc.left = Screen.FromControl(this).WorkingArea.Left;
abd.rc.right = Screen.FromControl(this).WorkingArea.Right;
if (abd.uEdge == (int)ABEdge.ABE_TOP)
{
abd.rc.top = Screen.FromControl(this).WorkingArea.Top;
abd.rc.bottom = Size.Height;
okraj = "top";
}
else
{
abd.rc.bottom = Screen.FromControl(this).WorkingArea.Bottom;
abd.rc.top = abd.rc.bottom - Size.Height;
okraj = "bottom";
}
}
SHAppBarMessage((int)ABMsg.ABM_QUERYPOS, ref abd);
switch (abd.uEdge)
{
case (int)ABEdge.ABE_LEFT:
abd.rc.right = abd.rc.left + Size.Width;
break;
case (int)ABEdge.ABE_RIGHT:
abd.rc.left = abd.rc.right - Size.Width;
break;
case (int)ABEdge.ABE_TOP:
abd.rc.bottom = abd.rc.top + Size.Height;
break;
case (int)ABEdge.ABE_BOTTOM:
abd.rc.top = abd.rc.bottom - Size.Height;
break;
}
this.Top -= 1;
SHAppBarMessage((int)ABMsg.ABM_SETPOS, ref abd);
MoveWindow(abd.hWnd, abd.rc.left, abd.rc.top, Size.Width, Size.Height, true);
toolBar = new Rectangle(abd.rc.left, abd.rc.top, Size.Width, Size.Height);
left = this.Left;
top = this.Top;
}
Untested
First, your SHAppBarMessage() declaration should be returning IntPtr:
[DllImport("SHELL32", CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr SHAppBarMessage(int dwMessage, ref APPBARDATA pData);
Then, as per the documentation for ABM_GETAUTOHIDEBAR and ABM_SETAUTOHIDEBAR, there can only be one AppBar with AutoHide per edge of the screen. So after filling out your APPBARDATA structure in your ABSetPos() method (specifying what edge you are going to use), you can query the system to see if there is already an auto-hiding bar on that edge. If none is returned, then you can register your appbar...something like:
IntPtr curAutoHide = SHAppBarMessage((int)ABMsg.ABM_GETAUTOHIDEBAR, ref abd);
if (curAutoHide.Equals(IntPtr.Zero)) // if there is no current appbar with autohide set for that edge...
{
abd.lParam = new IntPtr(1); // true
SHAppBarMessage((int)ABMsg.ABM_SETAUTOHIDEBAR, ref abd);
}
After many unsuccessful attempts, I decided to resolve the autohide in a different way. If I set appBar as an autohide, I will register it to the defined edge, but change its size from that edge to only 5 pixels, and all the controlls in it sets to visible = false and I set the appBar background to black color. From now on, I check the position of the mouse and as soon as the mouse moves to the appbar, appBar will once again grow to its original size - but NOT re-register, only edit size and set controlls to visible = true!! When mouse leav appBar, set size a visible of controlls back. It is very simple and after tests, both practical and trouble-free.
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.
I'm having a little bit trouble bringing together the old windows forms world and the WPF world. The original problem was that we always have to have a WindowsFormsHost for hosting our windows forms controls, especially the Chart control for displaying graphs.
Since we also have the need for client/server support, my idea was to create a windows form and add the Chart as a child. For this reason I created a OffscreenForm (BackEnd) which I shift to a location where the window is not visible (e.g. X=-10000, Y=-10000).
Then I attach my FrontEnd WPF control to a userdefined OnPaint method that updates everytime the BackEnd has changed. The problem in OnPaint is that is only renders the visible part. Therefore I took OnPrint, but DrawToImage has a memory bug and doesn't clean up correctly. I also transferred mouse events from the FrontEnd to the BackEnd.
In short words: Has someone ever tried to create something similar. Most of my current solution works fine, but there are minor things I don't really understand between OnPaint/OnPrint.
Here's my code. It looks currently a little bit ugly. Some of the things are only to see something when something happens.
It would be great if someone has a solution to get the image of the control when a refresh/repaint occurs.
Thanks
Martin
BackEnd:
using siemens.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace formshost
{
public class OffscreenWindowsFormsRenderer : Control
{
public event EventHandler<MemoryStream> MyPaint;
System.Windows.Forms.Form form = new System.Windows.Forms.Form();
List<Point> pts = new List<Point>();
int x = 10;
public OffscreenWindowsFormsRenderer(Control ctrl)
: base()
{
Application.EnableVisualStyles();
form = new System.Windows.Forms.Form();
form.CreateControl();
form.SetBounds(-10000, 300, 0, 0, System.Windows.Forms.BoundsSpecified.Location);
form.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
form.Show();
form.Controls.Add(this);
CreateControl();
BackColor = Color.Lime;
Controls.Add(ctrl);
//FireImage();
Timer T = new Timer();
T.Tick += T_Tick;
T.Start();
}
void T_Tick(object sender, EventArgs e)
{
Refresh();
x += 5;
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Yellow, x, x, 50, 50);
lock (pts)
{
if (pts.Count > 1)
e.Graphics.DrawLines(Pens.Red, pts.ToArray());
}
FireImage();
}
protected override void OnPrint(PaintEventArgs e)
{
base.OnPrint(e);
//FireImage();
}
Bitmap bmp;
void FireImage()
{
lock (this)
{
int W = Width;
int H = Height;
if (bmp != null)
{
if (bmp.Width != W || bmp.Height != H)
{
bmp.Dispose();
bmp = null;
}
}
if (bmp == null)
{
if (W > 0 && H > 0)
bmp = new Bitmap(W, H, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
}
if (bmp != null)
{
DrawToBitmapExtended(bmp, new Rectangle(0, 0, W, H));
MemoryStream mem = new MemoryStream();
bmp.Save(mem, ImageFormat.Bmp);
MyPaint(this, mem);
}
// using(Graphics g
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
FireImage();
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
Refresh();
}
public void MouseMove(IntPtr wparam, IntPtr lparam)
{
//Message msg = Message.Create(this.Handle, User.WM_MOUSEMOVE, wparam, lparam);
SendMessage(this.Handle, User.WM_MOUSEMOVE, wparam, lparam);
FireImage();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
lock (pts)
{
pts.Add(e.Location);
}
}
Bitmap tempimage;
public void DrawToBitmapExtended(Bitmap bitmap, Rectangle targetBounds)
{
if (bitmap == null)
{
throw new ArgumentNullException("bitmap");
}
if (targetBounds.Width <= 0 || targetBounds.Height <= 0
|| targetBounds.X < 0 || targetBounds.Y < 0)
{
throw new ArgumentException("targetBounds");
}
if (!IsHandleCreated)
{
CreateHandle();
}
int width = Math.Min(this.Width, targetBounds.Width);
int height = Math.Min(this.Height, targetBounds.Height);
if (tempimage != null)
{
if (tempimage.Width != width || tempimage.Height != height)
{
tempimage.Dispose();
tempimage = null;
}
}
// if (tempimage == null)
tempimage = new Bitmap(width, height, bitmap.PixelFormat);
using (Graphics g = Graphics.FromImage(tempimage))
{
IntPtr hDc = g.GetHdc();
//send the actual wm_print message
SendMessage(this.Handle, WM_PRINT, (IntPtr)hDc,
(IntPtr)(PRF_CHILDREN | PRF_CLIENT | PRF_NONCLIENT ));
//now BLT the result to the destination bitmap.
using (Graphics destGraphics = Graphics.FromImage(bitmap))
{
IntPtr desthDC = destGraphics.GetHdc();
BitBlt(desthDC, targetBounds.X, targetBounds.Y, width, height,
hDc, 0, 0, 0x00CC0020);
destGraphics.ReleaseHdc(desthDC);
}
g.ReleaseHdc(hDc);
}
}
const int PRF_NONCLIENT = 0x00000002,
PRF_CLIENT = 0x00000004,
PRF_ERASEBKGND = 0x00000008,
PRF_CHILDREN = 0x00000010,
WM_PRINT = 0x0317,
WM_PRINTCLIENT = 0x0318,
PRF_OWNED = 0x0020;
[DllImport("gdi32.dll")]
static extern int SetROP2(IntPtr hdc, int fnDrawMode);
[DllImport("User32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
[ResourceExposure(ResourceScope.None)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("Gdi32.dll", SetLastError = true, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
[ResourceExposure(ResourceScope.None)]
public static extern int BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight,
IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);
}
}
FrontEnd:
using formshost;
using siemens.Win32;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication6
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
OffscreenWindowsFormsRenderer off;
System.Windows.Forms.PictureBox box = new System.Windows.Forms.PictureBox();
public MainWindow()
{
InitializeComponent();
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
PanelFast fast = new PanelFast();
myGrid.Children.Add(fast);
Grid.SetRow(fast, 1);
box.Width = 100;
box.Height = 100;
box.Image = new Bitmap("D:\\image.jpg");
//box.Dock = System.Windows.Forms.DockStyle.Fill;
box.Show();
off = new OffscreenWindowsFormsRenderer(box);
off.Visible = true;
fast.SetRenderer(off);
}
}
public class PanelFast : Panel
{
public OffscreenWindowsFormsRenderer Renderer
{
get;
private set;
}
BitmapImage myBitmap = null;
public void SetRenderer(OffscreenWindowsFormsRenderer r)
{
Renderer = r;
Renderer.MyPaint += off_MyPaint;
}
void off_MyPaint(object sender, MemoryStream e)
{
myBitmap = new BitmapImage();
myBitmap.BeginInit();
myBitmap.StreamSource = e;
myBitmap.EndInit();
myBitmap.Freeze();
SnapsToDevicePixels = true;
InvalidateVisual();
}
public PanelFast()
{
SizeChanged += PanelFast_SizeChanged;
}
void PanelFast_SizeChanged(object sender, SizeChangedEventArgs e)
{
Renderer.SetBounds(0, 0, (int)ActualWidth, (int)ActualHeight, System.Windows.Forms.BoundsSpecified.Size);
}
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
dc.DrawImage(myBitmap, new Rect(0, 0, ActualWidth, ActualHeight));
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
Renderer.MouseMove((IntPtr)0, (IntPtr)GetXY(e.GetPosition(this)));
}
int GetXY(System.Windows.Point pt)
{
int x = (int)(pt.X);
int y = (int)(pt.Y);
return y << 16 | x;
}
}
}
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