I wrote code to display the text in separate pages, like Microsoft Word, I use a Collection of text boxes, and when the user filled one text box, new box is displayed automatically, and the cursor moves to her.
The problem is that when the user writes the last line in the text box, the box scrolls down a bit, as you will see when you will run this code, so how can I disable the scrolling.
the code :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
List<myRTB> pages; // collection of our RichTextBox, use as pages
public Form1()
{
InitializeComponent();
pages = new List<myRTB>();
pages.Add(new myRTB());
pages[0].Width = 200;
pages[0].Height = 290;
pages[0].Location = new Point(50, 10);
pages[0].Name = "0";
this.Controls.Add(pages[0]);
this.Width = 300;
this.Height = 360;
this.AutoScroll = true;
}
public void AddPage(int correntPageIndex)
{
if (correntPageIndex == (pages.Count - 1))
{
// create a new page
pages.Add(new myRTB());
pages[correntPageIndex + 1].Width = 200;
pages[correntPageIndex + 1].Height = 290;
pages[correntPageIndex + 1].Location = new Point(50, pages[correntPageIndex].Location.Y + 300);
this.Controls.Add(pages[pages.Count - 1]);
this.Name = (correntPageIndex + 1).ToString();
}
bool CursorInEnd = (pages[correntPageIndex].SelectionStart == pages[correntPageIndex].TextLength);
// Transfer the last word on the previous page, to the new page
int lastLineIndex = pages[correntPageIndex].GetLineFromCharIndex(pages[correntPageIndex].TextLength - 2);
// find the index of the first char in the last line
int indexOfFirstCharInLastLine = pages[correntPageIndex].GetFirstCharIndexFromLine(lastLineIndex);
// find the index of the last space in the last line
int indexOfLastSpace = pages[correntPageIndex].Text.LastIndexOf(' ', indexOfFirstCharInLastLine);
string restOfString;
if (indexOfLastSpace < 0) // no spaces in the last line
{
restOfString = pages[correntPageIndex].Text.Substring(pages[correntPageIndex].TextLength - 1);
pages[correntPageIndex + 1].Text.Insert(0, restOfString);
pages[correntPageIndex].Text.Remove(pages[correntPageIndex].TextLength - 1);
}
else // there is spaces in the last line
{
restOfString = pages[correntPageIndex].Text.Substring(indexOfLastSpace + 1);
pages[correntPageIndex + 1].Text = pages[correntPageIndex + 1].Text.Insert(0, restOfString);
pages[correntPageIndex].Text = pages[correntPageIndex].Text.Remove(indexOfLastSpace + 1);
}
if (CursorInEnd)
{
// Move the cursor to next page
pages[correntPageIndex + 1].SelectionStart = restOfString.Length;
pages[correntPageIndex + 1].Focus();
}
}
}
class myRTB : RichTextBox
{
public myRTB()
{
this.ScrollBars = RichTextBoxScrollBars.None;
}
protected override void WndProc(ref Message m)
{
// catch the request resize message
if (m.Msg == (WM_REFLECT | WM_NOTIFY))
{
REQRESIZE rrs = (REQRESIZE)(Marshal.PtrToStructure(m.LParam, typeof(REQRESIZE)));
if (rrs.nmhdr.code == EN_REQUESTRESIZE)
{
if (rrs.rc.ToRectangle().Height > this.ClientRectangle.Height)
{
((Form1)Parent).AddPage(int.Parse(this.Name));
}
}
}
base.WndProc(ref m);
}
[StructLayout(LayoutKind.Sequential)]
public struct NMHDR
{
public IntPtr HWND;
public uint idFrom;
public int code;
public override String ToString()
{
return String.Format("Hwnd: {0}, ControlID: {1}, Code: {2}",
HWND, idFrom, code);
}
}
[StructLayout(LayoutKind.Sequential)]
public struct REQRESIZE
{
public NMHDR nmhdr;
public RECT rc;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left, Top, Right, Bottom;
public override string ToString()
{
return String.Format("{0}, {1}, {2}, {3}", Left, Top, Right,
Bottom);
}
public Rectangle ToRectangle()
{
return Rectangle.FromLTRB(Left, Top, Right, Bottom);
}
}
public const int WM_USER = 0x400;
public const int WM_NOTIFY = 0x4E;
public const int WM_REFLECT = WM_USER + 0x1C00;
public const int EN_REQUESTRESIZE = 0x701;
}
}
To ensure that the text does not scroll automatically, take a look at the following answer to a similar problem.
Disabling RichTextBox autoscroll
Here's another great answer to your problem:
Prevent Autoscrolling in RichTextBox
I copied over the code from the link above, please ensure to give that user credit for providing this code (its not mine)
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, Int32 wParam, Int32 lParam);
const int WM_USER = 0x400;
const int EM_HIDESELECTION = WM_USER + 63;
void OnAppend(string text)
{
bool focused = richTextBox1.Focused;
//backup initial selection
int selection = richTextBox1.SelectionStart;
int length = richTextBox1.SelectionLength;
//allow autoscroll if selection is at end of text
bool autoscroll = (selection==richTextBox1.Text.Length);
if (!autoscroll)
{
//shift focus from RichTextBox to some other control
if (focused)
textBox1.Focus();
//hide selection
SendMessage(richTextBox1.Handle, EM_HIDESELECTION, 1, 0);
}
richTextBox1.AppendText(text);
if (!autoscroll)
{
//restore initial selection
richTextBox1.SelectionStart = selection;
richTextBox1.SelectionLength = length;
//unhide selection
SendMessage(richTextBox1.Handle, EM_HIDESELECTION, 0, 0);
//restore focus to RichTextBox
if(focused) richTextBox1.Focus();
}
}
Related
UI should remain same I don't want to do this:
dateTimePicker1.Format = DateTimePickerFormat.Custom;
dateTimePicker1.CustomFormat = "mm-yyyy";
dateTimePicker1.ShowUpDown = true;
A User can pick the year and month, change DateTimePicker to Month/Year picker, the MonthCalendar too.
I don't want to use a third-party control but I will if it will be the only answer point me to any free month/year picker control.
Here's a simple DateTimePicker custom control that pre-defines a custom DateTime format and modified the MonthCalendar view, configuring it show just the Months and the Year when is opened.
I've added a public property, ShowToday, that allows to specify whether the MonthCalendar popup should show the Today date at the bottom of the Calendar interface.
The custom control overrides OnDropDown to get the Handle of the MonthCalendar popup, sending a DTM_GETMONTHCAL message and changes its current view sending a MCM_SETCURRENTVIEW, specifying MCMV_YEAR as the value parameter.
This is how it look like:
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
[DesignerCategory("Code")]
class DateTimePickerYearMonth : DateTimePicker
{
public DateTimePickerYearMonth() {
this.CustomFormat = "MM-yyyy";
this.Format = DateTimePickerFormat.Custom;
this.Value = DateTime.Now;
}
[
Browsable(true), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(false),
Category("Appearance"),
Description("Shows or hides \"Today\" date at the bottom of the Calendar Control")
]
public bool ShowToday {
get => m_ShowToday;
set {
if (value != m_ShowToday) {
m_ShowToday = value;
ShowMonCalToday(m_ShowToday);
}
}
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
ShowMonCalToday(m_ShowToday);
}
protected override void OnDropDown(EventArgs e)
{
var hWnd = SendMessage(this.Handle, DTM_GETMONTHCAL, 0, 0);
if (hWnd != IntPtr.Zero) {
SendMessage(hWnd, MCM_SETCURRENTVIEW, 0, (int)MonCalView.MCMV_YEAR);
}
base.OnDropDown(e);
}
private void ShowMonCalToday(bool show)
{
int styles = SendMessage(this.Handle, DTM_GETMCSTYLE, 0, 0).ToInt32();
styles = show ? styles &~(int)MonCalStyles.MCS_NOTODAY : styles | (int)MonCalStyles.MCS_NOTODAY;
SendMessage(this.Handle, DTM_SETMCSTYLE, 0, styles);
}
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
private const int DTM_FIRST = 0x1000;
private const int DTM_GETMONTHCAL = DTM_FIRST + 8;
private const int DTM_SETMCSTYLE = DTM_FIRST + 11;
private const int DTM_GETMCSTYLE = DTM_FIRST + 12;
private const int MCM_FIRST = 0x1000;
private const int MCM_GETCURRENTVIEW = MCM_FIRST + 22;
private const int MCM_SETCURRENTVIEW = MCM_FIRST + 32;
private bool m_ShowToday = false;
public enum MonCalView : int
{
MCMV_MONTH = 0,
MCMV_YEAR = 1,
MCMV_DECADE = 2,
MCMV_CENTURY = 3
}
public enum MonCalStyles : int
{
MCS_DAYSTATE = 0x0001,
MCS_MULTISELECT = 0x0002,
MCS_WEEKNUMBERS = 0x0004,
MCS_NOTODAYCIRCLE = 0x0008,
MCS_NOTODAY = 0x0010,
MCS_NOTRAILINGDATES = 0x0040,
MCS_SHORTDAYSOFWEEK = 0x0080,
MCS_NOSELCHANGEONNAV = 0x0100
}
}
The custom format is to be used to determine the way the date should be captured in the system/database irrespective of what the user selected, it is not used to restrict the user to choosing either month or year.
I am creating a form inside another form. In the child form I have created taskbar button. I am overriding WndProc in the button.I am referring https://www.codeproject.com/Articles/10171/Adding-a-Minimize-to-tray-button-to-a-Form-s-caption bar. But whenever I am getting messages(mouse coordinates) , it is given with respect to the parent form. This makes trouble in calculations .I want those with respect to child form.How to achieve this?
This is the code for form1
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Form2 form2 = new Form2();
form2.TopLevel = false;
form2.AutoScroll = true;
form2.Anchor = AnchorStyles.Top;
form2.Dock = DockStyle.Fill;
this.splitContainer1.Panel2.Controls.Add(form2);
form2.Show();
}
}
this is form2.
public partial class Form2 : Form
{
TyronM.MinTrayBtn Pin_Button;
public Form2()
{
InitializeComponent();
Pin_Button = new TyronM.MinTrayBtn(this);
Pin_Button.MinTrayBtnClicked += new TyronM.MinTrayBtnClickedEventHandler(this.Pin_Button_Clicked);
}
public void Pin_Button_Clicked(object sender, EventArgs e)
{
MessageBox.Show("Pin button got Clicked");
}
protected override void WndProc(ref Message message)
{
const int WM_SYSCOMMAND = 0x0112;
const int SC_MOVE = 0xF010;
switch (message.Msg)
{
case WM_SYSCOMMAND:
int command = message.WParam.ToInt32() & 0xfff0;
if (command == SC_MOVE)
return;
break;
}
base.WndProc(ref message);
}
}
And this is for button.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Diagnostics;
using System.Resources;
using System.Runtime.InteropServices;
namespace TyronM {
public delegate void MinTrayBtnClickedEventHandler(object sender, EventArgs e);
/// <summary>
/// Summary description for Class.
/// </summary>
public class MinTrayBtn : NativeWindow {
bool pinned = false;
bool pressed = false;
Size wnd_size = new Size();
public bool captured;
Form parent;
public event MinTrayBtnClickedEventHandler MinTrayBtnClicked;
#region Constants
const int WM_SIZE = 5;
const int WM_SYNCPAINT = 136;
const int WM_MOVE = 3;
const int WM_ACTIVATE = 6;
const int WM_LBUTTONDOWN =513;
const int WM_LBUTTONUP =514;
const int WM_LBUTTONDBLCLK =515;
const int WM_MOUSEMOVE = 512;
const int WM_PAINT = 15;
const int WM_GETTEXT = 13;
const int WM_NCCREATE =129;
const int WM_NCLBUTTONDOWN = 161;
const int WM_NCLBUTTONUP = 162;
const int WM_NCMOUSEMOVE = 160;
const int WM_NCACTIVATE =134;
const int WM_NCPAINT = 133;
const int WM_NCHITTEST = 132;
const int WM_NCLBUTTONDBLCLK = 163;
const int VK_LBUTTON = 1;
const int SM_CXSIZE = 30;
const int SM_CYSIZE = 31;
#endregion
#region Extra Constants
const int WM_MBUTTONUP = 0x0208;
#endregion
#region WinAPI Imports
[DllImport("user32")]
public static extern int GetWindowDC(int hwnd);
[DllImport("user32")]
public static extern short GetAsyncKeyState(int vKey);
[DllImport("user32")]
public static extern int SetCapture(int hwnd);
[DllImport("user32")]
public static extern bool ReleaseCapture();
[DllImport("user32")]
public static extern int GetSysColor(int nIndex);
[DllImport("user32")]
public static extern int GetSystemMetrics(int nIndex);
#endregion
#region Constructor and Handle-Handler ^^
public MinTrayBtn(Form parent) {
parent.HandleCreated += new EventHandler(this.OnHandleCreated);
parent.HandleDestroyed+= new EventHandler(this.OnHandleDestroyed);
parent.TextChanged+= new EventHandler(this.OnTextChanged);
this.parent = parent;
}
// Listen for the control's window creation and then hook into it.
internal void OnHandleCreated(object sender, EventArgs e){
// Window is now created, assign handle to NativeWindow.
AssignHandle(((Form)sender).Handle);
}
internal void OnHandleDestroyed(object sender, EventArgs e) {
// Window was destroyed, release hook.
ReleaseHandle();
}
// Changing the Text invalidates the Window, so we got to Draw the Button again
private void OnTextChanged(object sender, EventArgs e) {
DrawButton();
}
#endregion
#region WndProc
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name="FullTrust")]
protected override void WndProc(ref Message m){
//label3.Text = "Button pressed: " + pressed;
//label4.Text = "Mouse captured: " + captured;
// Change the Pressed-State of the Button when the User pressed the
// left mouse button and moves the cursor over the button
if (m.Msg == WM_LBUTTONDBLCLK ||m.Msg == WM_LBUTTONDOWN || m.Msg == WM_LBUTTONUP
||m.Msg == WM_MBUTTONUP
)
MessageBox.Show("click happened");
if(m.Msg==WM_MOUSEMOVE) {
Point pnt2 = new Point((int)m.LParam);
Size rel_pos2 = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
// Not needed because SetCapture seems to convert the cordinates anyway
//pnt2 = PointToClient(pnt2);
pnt2-=rel_pos2;
//label2.Text = "Cursor #"+pnt2.X+"/"+pnt2.Y;
if(pressed) {
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
//pnt = PointToClient(pnt);
pnt-=rel_pos;
if(!MouseinBtn(pnt)) {
pressed = false;
DrawButton();
}
} else {
if((GetAsyncKeyState(VK_LBUTTON)&(-32768))!=0) {
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
//pnt = PointToClient(pnt);
pnt-=rel_pos;
if(MouseinBtn(pnt)) {
pressed = true;
DrawButton();
}
}
}
}
// Ignore Double-Clicks on the Traybutton
if(m.Msg==WM_NCLBUTTONDBLCLK) {
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
pnt = parent.PointToClient(pnt);
pnt-=rel_pos;
if(MouseinBtn(pnt)) {
return;
}
}
#region NOT WORKING
// Button released and eventually clicked
if(m.Msg==WM_LBUTTONUP)
{
ReleaseCapture();
captured = false;
if(pressed) {
pressed = false;
DrawButton();
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
pnt-=rel_pos;
if(MouseinBtn(pnt)) {
//TrayButton_clicked();
EventArgs e = new EventArgs();
if (MinTrayBtnClicked != null)
MinTrayBtnClicked(this, e);
return;
}
}
}
#endregion
// Clicking the Button - Capture the Mouse and await until the Uses relases the Button again
if(m.Msg==WM_NCLBUTTONDOWN) {
//MessageBox.Show("clicked");
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
pnt = parent.PointToClient(pnt);
pnt-=rel_pos;
if(MouseinBtn(pnt)) {
MessageBox.Show("clicked");
pressed = true;
DrawButton();
SetCapture((int)parent.Handle);
captured = true;
return;
}
}
// Drawing the Button and getting the Real Size of the Window
if(m.Msg == WM_ACTIVATE || m.Msg==WM_SIZE || m.Msg==WM_SYNCPAINT || m.Msg==WM_NCACTIVATE || m.Msg==WM_NCCREATE || m.Msg==WM_NCPAINT || m.Msg==WM_NCACTIVATE || m.Msg==WM_NCHITTEST || m.Msg==WM_PAINT) {
if(m.Msg==WM_SIZE) wnd_size = new Size(new Point((int)m.LParam));
DrawButton();
}
base.WndProc(ref m);
}
#endregion
#region Button-Specific Functions
public bool MouseinBtn(Point click) {
int btn_width = GetSystemMetrics(SM_CXSIZE);
int btn_height = GetSystemMetrics(SM_CYSIZE);
Size btn_size = new Size(btn_width, btn_height);
Point pos = new Point(wnd_size.Width - 3 * btn_width - 12 - (btn_width - 18)+7, 6+4);
return click.X>=pos.X && click.X<=pos.X+btn_size.Width &&
click.Y>=pos.Y && click.Y<=pos.Y+btn_size.Height;
}
public void DrawButton() {
Graphics g = Graphics.FromHdc((IntPtr)GetWindowDC((int)parent.Handle)); //m.HWnd));
DrawButton(g, pressed);
}
public void DrawButton(Graphics g, bool pressed) {
int btn_width = GetSystemMetrics(SM_CXSIZE);
int btn_height = GetSystemMetrics(SM_CYSIZE);
//Point pos = new Point(wnd_size.Width-3*btn_width-12-(btn_width-18),6);
Point pos = new Point(wnd_size.Width - 3 * btn_width - 12 - (btn_width - 18)+7, 6+4);
// real button size
btn_width-=2;
btn_height-=4;
Color light = SystemColors.ControlLightLight;
Color icon = SystemColors.ControlText;
Color background = SystemColors.Control;
Color shadow1 = SystemColors.ControlDark;
Color shadow2 = SystemColors.ControlDarkDark;
Color tmp1, tmp2;
if(pressed) {
tmp1 = shadow2;
tmp2 = light;
} else {
tmp1 = light;
tmp2 = shadow2;
}
g.DrawLine(new Pen(tmp1),pos, new Point(pos.X+btn_width-1,pos.Y));
g.DrawLine(new Pen(tmp1),pos, new Point(pos.X,pos.Y+btn_height-1));
if(pressed) {
g.DrawLine(new Pen(shadow1),pos.X+1, pos.Y+1, pos.X+btn_width-2, pos.Y+1);
g.DrawLine(new Pen(shadow1),pos.X+1, pos.Y+1, pos.X+1, pos.Y+btn_height-2);
} else {
g.DrawLine(new Pen(shadow1),pos.X+btn_width-2, pos.Y+1, pos.X+btn_width-2, pos.Y+btn_height-2);
g.DrawLine(new Pen(shadow1),pos.X+1, pos.Y+btn_height-2, pos.X+btn_width-2, pos.Y+btn_height-2);
}
g.DrawLine(new Pen(tmp2),pos.X+btn_width-1, pos.Y+0, pos.X+btn_width-1, pos.Y+btn_height-1);
g.DrawLine(new Pen(tmp2),pos.X+0, pos.Y+btn_height-1, pos.X+btn_width-1, pos.Y+btn_height-1);
g.FillRectangle(new SolidBrush(background),pos.X+1+Convert.ToInt32(pressed), pos.Y+1+Convert.ToInt32(pressed), btn_width-3,btn_height-3);
#region Added Code
g.FillRectangle(new SolidBrush(icon),pos.X+(float)0.5625*btn_width+Convert.ToInt32(pressed),pos.Y+(float)0.6428*btn_height+Convert.ToInt32(pressed),btn_width*(float)0.1875,btn_height*(float)0.143);
g.DrawImage(Image.FromFile("red_Pushpin.jfif"),new Rectangle( pos,new Size(btn_width,btn_height)));
#endregion
}
#endregion
}
}
This is code for main
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
This is the screenshot of the winform.
EDIT:
Upon investigation I found that WM_LBUTTONUP(Mouse left button up) is not triggered. Or it is not coming inside WndProc.
EDIT1:
It seems there was a problem in brackets.I changed the if conditions into switch case. This somewhat cleared the "Mouse left button up" problem. Now it is properly triggering. The remaining problem is mouse points based on parent form.
I'm writing a console application that needs user input on the bottom line while text is scrolling. The idea is to have text scroll and leave an input line at the bottom. I want text editing functionality (arrow-keys, insert, delete, etc). I'd love to be able to have static "status lines" too (lines unaffected by scrolling).
A real world example would be Irssi:
In my code I'm hooking up to NLog and writing its output to screen while also providing an input line to the user. It is done by "pausing input" on write: using Console.MoveBufferArea to move text up, disabling cursor, repositioning cursor, writing log-text, repositioning cursor back to input line and enabling cursor. It almost works, but there are some problems:
It is very slow. In cases where I write 20-30 lines the application slows down considerably. (Can be solved with buffering incoming, but won't solve scroll speed.)
Lines that overflow (i.e. exception stacktrace) leaves a line at the very bottom of the screen.
Lines that overflow are (partially) overwritten as text scrolls up. This also messes up input line.
Scrolling up/down does not work.
Is there a library to help me do this?
If not then how do I fix speed? How do I fix scrolling?
Cross platform solution preferred.
public class InputConsole
{
private static readonly object _bufferLock = new object();
private static int _windowWidth = Console.BufferWidth;
private static int _windowHeight = Console.BufferHeight;
private static int _windowLeft = Console.WindowLeft;
private static int _windowTop = Console.WindowTop;
public InputConsole()
{
MethodCallTarget target = new MethodCallTarget();
target.ClassName = typeof(InputConsole).AssemblyQualifiedName;
target.MethodName = "LogMethod";
target.Parameters.Add(new MethodCallParameter("${level}"));
target.Parameters.Add(new MethodCallParameter("${message}"));
target.Parameters.Add(new MethodCallParameter("${exception:format=tostring}"));
target.Parameters.Add(new MethodCallParameter("[${logger:shortName=true}]"));
SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Trace);
try
{
Console.SetWindowSize(180, 50);
Console.SetBufferSize(180, 50);
_windowWidth = Console.BufferWidth;
_windowHeight = Console.BufferHeight;
}
catch (Exception exception)
{
Console.WriteLine("Unable to resize console: " + exception);
}
}
public void Run()
{
string input;
do
{
lock (_bufferLock)
{
Console.SetCursorPosition(0, _windowHeight - 1);
Console.Write("Command: ");
Console.CursorVisible = true;
}
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.Yellow;
input = Console.ReadLine();
lock (_bufferLock)
{
Console.CursorVisible = false;
}
} while (!string.Equals(input, "quit", StringComparison.OrdinalIgnoreCase));
}
public static void LogMethod(string level, string message, string exception, string caller)
{
if (Console.BufferHeight == _windowHeight)
Console.MoveBufferArea(_windowLeft, _windowTop + 1, Console.BufferWidth, Console.BufferHeight - 2, _windowLeft, _windowTop);
var fgColor = ConsoleColor.White;
var bgColor = ConsoleColor.Black;
switch (level.ToUpper())
{
case "TRACE":
fgColor = ConsoleColor.DarkGray;
break;
case "DEBUG":
fgColor = ConsoleColor.Gray;
break;
case "INFO":
fgColor = ConsoleColor.White;
break;
case "WARNING":
fgColor = ConsoleColor.Cyan;
break;
case "ERROR":
fgColor = ConsoleColor.White;
bgColor = ConsoleColor.Red;
break;
}
var str = string.Format("({0}) {1} {2} {3}", level.ToUpper(), caller, message, exception);
WriteAt(_windowLeft, _windowHeight - 3, str, fgColor, bgColor);
}
public static void WriteAt(int left, int top, string s, ConsoleColor foregroundColor = ConsoleColor.White, ConsoleColor backgroundColor = ConsoleColor.Black)
{
lock (_bufferLock)
{
var currentBackgroundColor = Console.BackgroundColor;
var currentForegroundColor = Console.ForegroundColor;
Console.BackgroundColor = backgroundColor;
Console.ForegroundColor = foregroundColor;
int currentLeft = Console.CursorLeft;
int currentTop = Console.CursorTop;
var currentVisible = Console.CursorVisible;
Console.CursorVisible = false;
Console.SetCursorPosition(left, top);
Console.Write(s);
Console.SetCursorPosition(currentLeft, currentTop);
Console.CursorVisible = currentVisible;
Console.BackgroundColor = currentBackgroundColor;
Console.ForegroundColor = currentForegroundColor;
}
}
}
Doing further research into text console in Windows I seems it is difficult to make it go faster. Through a custom implementation with lower redraw rates (less to WriteConsoleOutput) I was able to get just over 10x speed increase over Console.WriteLine.
However since Console.WriteLine enforces the "scroll everything when we reach bottom" I was using Console.MoveBufferArea. Tests shows that my implementation of MoveBufferArea (included in my original question) was around 90x slower than Console.WriteLine. With my new implementation using WriteConsoleOutput I was however able to get a 1356x speed increase over MoveBufferedArea.
Since it was a bit difficult to find information about it I have detailed my finding in a blog post. I'm also attaching the code to this answer for posterity.
I have written a class that allows me to scroll individual boxes. I have also implemented a line input system to emulate that of standard Console.ReadLine();. Note that this implementation is missing home/end-support (easy to fix though).
Note that to get any speed increase from it you have to set box.AutoRedraw = false; and manually call box.Draw(); regularly. With box.AutoRedraw = true; (calling Draw() on every Write()) this solution is actually 30 times slower than Console.WriteLine and 3 times faster than MoveBufferArea.
Example on how to use:
_logBox = new InputConsoleBox(0, 0, (short)Console.BufferWidth, (short)(Console.BufferHeight - 2), InputConsoleBox.Colors.LightWhite, InputConsoleBox.Colors.Black);
_statusBox = new InputConsoleBox(0, (short)(Console.BufferHeight - 3), (short)Console.BufferWidth, 1, InputConsoleBox.Colors.LightYellow, InputConsoleBox.Colors.DarkBlue);
_inputBox = new InputConsoleBox(0, (short)(Console.BufferHeight - 2), (short)Console.BufferWidth, 1, InputConsoleBox.Colors.LightYellow, InputConsoleBox.Colors.Black);
_statusBox.WriteLine("Hey there!");
_inputBox.InputPrompt = "Command: ";
// If you are okay with some slight flickering this is an easy way to set up a refresh timer
_logBox.AutoDraw = false;
_redrawTask = Task.Factory.StartNew(async () =>
{
while (true)
{
await Task.Delay(100);
if (_logBox.IsDirty)
_logBox.Draw();
}
});
// Line input box
var line = _inputBox.ReadLine(); // Blocking while waiting for <enter>
Code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32.SafeHandles;
public class InputConsoleBox
{
#region Output
#region Win32 interop
private const UInt32 STD_OUTPUT_HANDLE = unchecked((UInt32)(-11));
private const UInt32 STD_ERROR_HANDLE = unchecked((UInt32)(-12));
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr GetStdHandle(UInt32 type);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern SafeFileHandle CreateFile(
string fileName,
[MarshalAs(UnmanagedType.U4)] uint fileAccess,
[MarshalAs(UnmanagedType.U4)] uint fileShare,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] int flags,
IntPtr template);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteConsoleOutput(
SafeFileHandle hConsoleOutput,
CharInfo[] lpBuffer,
Coord dwBufferSize,
Coord dwBufferCoord,
ref SmallRect lpWriteRegion);
[StructLayout(LayoutKind.Sequential)]
private struct Coord
{
public short X;
public short Y;
public Coord(short X, short Y)
{
this.X = X;
this.Y = Y;
}
};
[StructLayout(LayoutKind.Explicit)]
private struct CharUnion
{
[FieldOffset(0)]
public char UnicodeChar;
[FieldOffset(0)]
public byte AsciiChar;
}
[StructLayout(LayoutKind.Explicit)]
private struct CharInfo
{
[FieldOffset(0)]
public CharUnion Char;
[FieldOffset(2)]
public ushort Attributes;
public CharInfo(char #char, ushort attributes)
{
this.Char = new CharUnion();
Char.UnicodeChar = #char;
Attributes = attributes;
}
}
[StructLayout(LayoutKind.Sequential)]
private struct SmallRect
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
#endregion
#region Colors Enum
private const int HighIntensity = 0x0008;
private const ushort COMMON_LVB_LEADING_BYTE = 0x0100;
private const ushort COMMON_LVB_TRAILING_BYTE = 0x0200;
private const ushort COMMON_LVB_GRID_HORIZONTAL = 0x0400;
private const ushort COMMON_LVB_GRID_LVERTICAL = 0x0800;
private const ushort COMMON_LVB_GRID_RVERTICAL = 0x1000;
private const ushort COMMON_LVB_REVERSE_VIDEO = 0x4000;
private const ushort COMMON_LVB_UNDERSCORE = 0x8000;
private const ushort COMMON_LVB_SBCSDBCS = 0x0300;
[Flags]
public enum Colors : int
{
Black = 0x0000,
DarkBlue = 0x0001,
DarkGreen = 0x0002,
DarkRed = 0x0004,
Gray = DarkBlue | DarkGreen | DarkRed,
DarkYellow = DarkRed | DarkGreen,
DarkPurple = DarkRed | DarkBlue,
DarkCyan = DarkGreen | DarkBlue,
LightBlue = DarkBlue | HighIntensity,
LightGreen = DarkGreen | HighIntensity,
LightRed = DarkRed | HighIntensity,
LightWhite = Gray | HighIntensity,
LightYellow = DarkYellow | HighIntensity,
LightPurple = DarkPurple | HighIntensity,
LightCyan = DarkCyan | HighIntensity
}
#endregion // Colors Enum
private readonly CharInfo[] _buffer;
private readonly List<CharInfo> _tmpBuffer;
private readonly short _left;
private readonly short _top;
private readonly short _width;
private readonly short _height;
private ushort _defaultColor;
private int _cursorLeft;
private int _cursorTop;
private static SafeFileHandle _safeFileHandle;
/// <summary>
/// Automatically draw to console.
/// Unset this if you want to manually control when (and what order) boxes are writen to consoles - or you want to batch some stuff.
/// You must manually call <c>Draw()</c> to write to console.
/// </summary>
public bool AutoDraw = true;
public bool IsDirty { get; private set; }
public InputConsoleBox(short left, short top, short width, short height, Colors defaultForegroundColor = Colors.Gray, Colors defaultBackgroundColor = Colors.Black)
{
if (left < 0 || top < 0 || left + width > Console.BufferWidth || top + height > Console.BufferHeight)
throw new Exception(string.Format("Attempting to create a box {0},{1}->{2},{3} that is out of buffer bounds 0,0->{4},{5}", left, top, left + width, top + height, Console.BufferWidth, Console.BufferHeight));
_left = left;
_top = top;
_width = width;
_height = height;
_buffer = new CharInfo[_width * _height];
_defaultColor = CombineColors(defaultForegroundColor, defaultBackgroundColor);
_tmpBuffer = new List<CharInfo>(_width * _height); // Assumption that we won't be writing much more than a screenful (backbufferfull) in every write operation
//SafeFileHandle h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
if (_safeFileHandle == null)
{
var stdOutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
_safeFileHandle = new SafeFileHandle(stdOutputHandle, false);
}
Clear();
Draw();
}
public void Clear()
{
for (int y = 0; y < _height; y++)
{
for (int x = 0; x < _width; x++)
{
var i = (y * _width) + x;
_buffer[i].Char.UnicodeChar = ' ';
_buffer[i].Attributes = _defaultColor;
}
}
IsDirty = true;
// Update screen
if (AutoDraw)
Draw();
}
public void Draw()
{
IsDirty = false;
var rect = new SmallRect() { Left = _left, Top = _top, Right = (short)(_left + _width), Bottom = (short)(_top + _height) };
bool b = WriteConsoleOutput(_safeFileHandle, _buffer,
new Coord(_width, _height),
new Coord(0, 0), ref rect);
}
private static ushort CombineColors(Colors foreColor, Colors backColor)
{
return (ushort)((int)foreColor + (((int)backColor) << 4));
}
public void SetCursorPosition(int left, int top)
{
if (left >= _width || top >= _height)
throw new Exception(string.Format("Position out of bounds attempting to set cursor at box pos {0},{1} when box size is only {2},{3}.", left, top, _width, _height));
_cursorLeft = left;
_cursorTop = top;
}
public void SetCursorBlink(int left, int top, bool state)
{
Console.SetCursorPosition(left, top);
Console.CursorVisible = state;
//// Does not work
//var i = (top * _width) + left;
//if (state)
// _buffer[i].Attributes = (ushort)((int)_buffer[i].Attributes & ~(int)COMMON_LVB_UNDERSCORE);
//else
// _buffer[i].Attributes = (ushort)((int)_buffer[i].Attributes | (int)COMMON_LVB_UNDERSCORE);
//if (AutoDraw)
// Draw();
}
public void WriteLine(string line, Colors fgColor, Colors bgColor)
{
var c = _defaultColor;
_defaultColor = CombineColors(fgColor, bgColor);
WriteLine(line);
_defaultColor = c;
}
public void WriteLine(string line)
{
Write(line + "\n");
}
public void Write(string text)
{
Write(text.ToCharArray());
}
public void Write(char[] text)
{
IsDirty = true;
_tmpBuffer.Clear();
bool newLine = false;
// Old-school! Could definitively have been done more easily with regex. :)
var col = 0;
var row = -1;
for (int i = 0; i < text.Length; i++)
{
// Detect newline
if (text[i] == '\n')
newLine = true;
if (text[i] == '\r')
{
newLine = true;
// Skip following \n
if (i + 1 < text.Length && text[i] == '\n')
i++;
}
// Keep track of column and row
col++;
if (col == _width)
{
col = 0;
row++;
if (newLine) // Last character was newline? Skip filling the whole next line with empty
{
newLine = false;
continue;
}
}
// If we are newlining we need to fill the remaining with blanks
if (newLine)
{
newLine = false;
for (int i2 = col; i2 <= _width; i2++)
{
_tmpBuffer.Add(new CharInfo(' ', _defaultColor));
}
col = 0;
row++;
continue;
}
if (i >= text.Length)
break;
// Add character
_tmpBuffer.Add(new CharInfo(text[i], _defaultColor));
}
var cursorI = (_cursorTop * _width) + _cursorLeft;
// Get our end position
var end = cursorI + _tmpBuffer.Count;
// If we are overflowing (scrolling) then we need to complete our last line with spaces (align buffer with line ending)
if (end > _buffer.Length && col != 0)
{
for (int i = col; i <= _width; i++)
{
_tmpBuffer.Add(new CharInfo(' ', _defaultColor));
}
col = 0;
row++;
}
// Chop start of buffer to fit into destination buffer
if (_tmpBuffer.Count > _buffer.Length)
_tmpBuffer.RemoveRange(0, _tmpBuffer.Count - _buffer.Length);
// Convert to array so we can batch copy
var tmpArray = _tmpBuffer.ToArray();
// Are we going to write outside of buffer?
end = cursorI + _tmpBuffer.Count;
var scrollUp = 0;
if (end > _buffer.Length)
{
scrollUp = end - _buffer.Length;
}
// Scroll up
if (scrollUp > 0)
{
Array.Copy(_buffer, scrollUp, _buffer, 0, _buffer.Length - scrollUp);
cursorI -= scrollUp;
}
var lastPos = Math.Min(_buffer.Length, cursorI + tmpArray.Length);
var firstPos = lastPos - tmpArray.Length;
// Copy new data in
Array.Copy(tmpArray, 0, _buffer, firstPos, tmpArray.Length);
// Set new cursor position
_cursorLeft = col;
_cursorTop = Math.Min(_height, _cursorTop + row + 1);
// Write to main buffer
if (AutoDraw)
Draw();
}
#endregion
#region Input
private string _currentInputBuffer = "";
private string _inputPrompt;
private int _inputCursorPos = 0;
private int _inputFrameStart = 0;
// Not used because COMMON_LVB_UNDERSCORE doesn't work
//private bool _inputCursorState = false;
//private int _inputCursorStateChange = 0;
private int _cursorBlinkLeft = 0;
private int _cursorBlinkTop = 0;
public string InputPrompt
{
get { return _inputPrompt; }
set
{
_inputPrompt = value;
ResetInput();
}
}
private void ResetInput()
{
SetCursorPosition(0, 0);
_inputCursorPos = Math.Min(_currentInputBuffer.Length, _inputCursorPos);
var inputPrompt = InputPrompt + "[" + _currentInputBuffer.Length + "] ";
// What is the max length we can write?
var maxLen = _width - inputPrompt.Length;
if (maxLen < 0)
return;
if (_inputCursorPos > _inputFrameStart + maxLen)
_inputFrameStart = _inputCursorPos - maxLen;
if (_inputCursorPos < _inputFrameStart)
_inputFrameStart = _inputCursorPos;
_cursorBlinkLeft = inputPrompt.Length + _inputCursorPos - _inputFrameStart;
//if (_currentInputBuffer.Length - _inputFrameStart < maxLen)
// _inputFrameStart--;
// Write and pad the end
var str = inputPrompt + _currentInputBuffer.Substring(_inputFrameStart, Math.Min(_currentInputBuffer.Length - _inputFrameStart, maxLen));
var spaceLen = _width - str.Length;
Write(str + (spaceLen > 0 ? new String(' ', spaceLen) : ""));
UpdateCursorBlink(true);
}
private void UpdateCursorBlink(bool force)
{
// Since COMMON_LVB_UNDERSCORE doesn't work we won't be controlling blink
//// Blink the cursor
//if (Environment.TickCount > _inputCursorStateChange)
//{
// _inputCursorStateChange = Environment.TickCount + 250;
// _inputCursorState = !_inputCursorState;
// force = true;
//}
//if (force)
// SetCursorBlink(_cursorBlinkLeft, _cursorBlinkTop, _inputCursorState);
SetCursorBlink(_left + _cursorBlinkLeft, _top + _cursorBlinkTop, true);
}
public string ReadLine()
{
Console.CursorVisible = false;
Clear();
ResetInput();
while (true)
{
Thread.Sleep(50);
while (Console.KeyAvailable)
{
var key = Console.ReadKey(true);
switch (key.Key)
{
case ConsoleKey.Enter:
{
var ret = _currentInputBuffer;
_inputCursorPos = 0;
_currentInputBuffer = "";
return ret;
break;
}
case ConsoleKey.LeftArrow:
{
_inputCursorPos = Math.Max(0, _inputCursorPos - 1);
break;
}
case ConsoleKey.RightArrow:
{
_inputCursorPos = Math.Min(_currentInputBuffer.Length, _inputCursorPos + 1);
break;
}
case ConsoleKey.Backspace:
{
if (_inputCursorPos > 0)
{
_inputCursorPos--;
_currentInputBuffer = _currentInputBuffer.Remove(_inputCursorPos, 1);
}
break;
}
case ConsoleKey.Delete:
{
if (_inputCursorPos < _currentInputBuffer.Length - 1)
_currentInputBuffer = _currentInputBuffer.Remove(_inputCursorPos, 1);
break;
}
default:
{
var pos = _inputCursorPos;
//if (_inputCursorPos == _currentInputBuffer.Length)
_inputCursorPos++;
_currentInputBuffer = _currentInputBuffer.Insert(pos, key.KeyChar.ToString());
break;
}
}
ResetInput();
}
// COMMON_LVB_UNDERSCORE doesn't work so we use Consoles default cursor
//UpdateCursorBlink(false);
}
}
#endregion
}
I am working on creating a simple notebook application. I have been asked to make the input area look like a sheet of notebook paper, with the text sitting on light blue lines. I am trying to make this work, but it seems to be failing miserably.
So far, I have created a transparent RichTextBox that sits on top of a panel. The Text Box is:
using System;
using System.Windows.Forms;
public class TransparentTextBox : RichTextBox
{
public TransparentTextBox()
{
this.SetStyle(ControlStyles.Opaque, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
}
protected override CreateParams CreateParams
{
get
{
CreateParams parms = base.CreateParams;
parms.ExStyle |= 0x20; // Turn on WS_EX_TRANSPARENT
return parms;
}
}
}
The paint code for the panel:
private void paper_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Clear(Color.White);
g.DrawLine(new Pen(Brushes.LightPink, 2), 20, 0, 20, paper.Height);
int h = TextRenderer.MeasureText("Testj", txtBody.Font).Height;
for (int x = 2 + h; x < paper.Height; x += h)
{
g.DrawLine(new Pen(Brushes.LightSkyBlue, 2), 0, x, paper.Width, x);
}
}
The lines are static, and they will grow to fit any font size/family that is chosen. The problem is when the text box is scrolled. The lines won't move with the text. I have tried to link the handle of the scroll bar to the lines, but they don't seem to be linking properly.
The code to get the current scroll position:
[StructLayout(LayoutKind.Sequential)]
public struct SCROLLINFO
{
public int cbSize;
public uint fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
}
public enum ScrollBarDirection
{
SB_HORZ = 0,
SB_VERT = 1,
SB_CTL = 2,
SB_BOTH = 3
}
public enum ScrollInfoMask
{
SIF_RANGE = 0x1,
SIF_PAGE = 0x2,
SIF_POS = 0x4,
SIF_DISABLENOSCROLL = 0x8,
SIF_TRACKPOS = 0x10,
SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS
}
...
public partial class Form1 : Form
{
[DllImport("User32.dll", EntryPoint = "GetScrollInfo")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetScrollInfo([In]IntPtr hwnd, [In]int fnBar, [In, Out]ref SCROLLINFO lpsi);
...
private void txtBody_VScroll(object sender, EventArgs e)
{
inf.cbSize = Marshal.SizeOf(inf);
inf.fMask = (int)ScrollInfoMask.SIF_ALL;
GetScrollInfo(txtBody.Handle, 1, ref inf);
Console.WriteLine(inf.nTrackPos + ":" + inf.nPos + ":" + TextRenderer.MeasureText("Testj", txtBody.Font).Height);
paper.Invalidate();
}
Then the paint above was modified to use this:
for (int x = inf.nPos % h; x < paper.Height; x += h)
{
g.DrawLine(new Pen(Brushes.LightSkyBlue, 2), 0, x, paper.Width, x);
}
I also tried to use nTrackPos, but neither seemed to follow the text like I want it to. I'm not too familiar with C#, so I wanted to know what I am missing/could do better. I am using Visual Studio 2008, with Visual C# 2008. .Net framework 3.5 SP1
So, here is what I came up with after some intensive googling. I decided to follow more into Gusman's comment on my question and look into drawing on the textbox again. After some playing, I realized I was improperly calculating the position of the start line. So, I reconfigured my custom RichTextBox to look like:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace Journal
{
class CustomRichTextBox : RichTextBox
{
private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;
private const int WM_MOUSEWHEEL = 0x20A;
private const int WM_PAINT = 0x00F;
private const int EM_GETSCROLLPOS = 0x4DD;
public int lineOffset = 0;
[DllImport("user32.dll")]
public static extern int SendMessage(
IntPtr hWnd,
int Msg,
IntPtr wParam,
ref Point lParam
);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
using (Graphics g = base.CreateGraphics())
{
Point p = new Point();
//get the position of the scrollbar to calculate the offset
SendMessage(this.Handle, EM_GETSCROLLPOS, IntPtr.Zero, ref p);
//draw the pink line on the side
g.DrawLine(new Pen(Brushes.LightPink, 2), 0, 0, 0, this.Height);
//determine how tall the text will be per line
int h = TextRenderer.MeasureText("Testj", this.Font).Height;
//calculate where the lines need to start
lineOffset = h - (p.Y % h);
//draw lines until there is no more box
for (int x = lineOffset; x < Height; x += h)
{
g.DrawLine(new Pen(Brushes.LightSkyBlue, 2), 0, x, Width, x);
}
//force the panel under us to draw itself.
Parent.Invalidate();
}
}
}
public CustomRichTextBox()
{
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
}
}
I then set this box inside of a panel to get the padding I want. The panel is forced to redraw itself with the text box.
I am trying to figure out the best way to populate address fields from a select list (vague but read on)..
The layout:
When I select the Address dropdown, I would like to see a nice list of full addresses, ie, with street name, country, postcode, etc. but as Im sure you are aware, combo's are one liners only.
Ideal scenario:
The result:
Has anyone a method for doing this?
Here is the full solution and as you can see, its perfectly what I wanted.
The ComboBoxEx is a class derived from a ComboBox which I have copied last. The reason for it is to set the height of the Items container (DropDownHeight). Without it, the container is calculated on the size of the first item x no. of items, and since the first item is zero height, the container would be zero height. So it needed a new class.
using System;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitializeComboBox();
}
private ComboxBoxEx cbox1 = new ComboxBoxEx();
private DataTable items = new DataTable();
private void InitializeComboBox()
{
items.Columns.AddRange(new DataColumn[] { new DataColumn("id"), new DataColumn("name"), new DataColumn("address") });
items.Rows.Add(new object[] { 0, "[Please choose an address]", "" });
items.Rows.Add(new object[] { 1, "Country", "Country" });
items.Rows.Add(new object[] { 2, "House name", "House name\nStreet name\nTown name\nPostcode\nCountry" });
items.Rows.Add(new object[] { 3, "House name", "House name\nStreet name\nTown name\nPostcode\nCountry" });
cbox1.Location = new Point(39, 20);
cbox1.Size = new System.Drawing.Size(198, 21);
cbox1.DrawMode = DrawMode.OwnerDrawVariable;
cbox1.DrawItem += new DrawItemEventHandler(comboBox2_DrawItem);
cbox1.MeasureItem += new MeasureItemEventHandler(comboBox2_MeasureItem);
cbox1.SelectedIndexChanged += new EventHandler(comboBox2_SelectedIndexChanged);
//cbox1.DropDownWidth = 250;
//cbox1.DropDownHeight = 300;
//cbox1.MaxDropDownItems = 6;
this.Controls.Add(cbox1);
cbox1.ValueMember = "id";
cbox1.DisplayMember = "name";
cbox1.DataSource = new BindingSource(items, null);
//cbox1.SelectedIndex = -1;
}
private void comboBox2_MeasureItem(object sender, MeasureItemEventArgs e)
{
ComboxBoxEx cbox = (ComboxBoxEx)sender;
DataRowView item = (DataRowView)cbox.Items[e.Index];
string txt = item["address"].ToString();
int height = Convert.ToInt32(e.Graphics.MeasureString(txt, cbox.Font).Height);
e.ItemHeight = height + 4;
e.ItemWidth = cbox.DropDownWidth;
cbox.ItemHeights.Add(e.ItemHeight);
}
private void comboBox2_DrawItem(object sender, DrawItemEventArgs e)
{
ComboxBoxEx cbox = (ComboxBoxEx)sender;
DataRowView item = (DataRowView)cbox.Items[e.Index];
string txt = item["address"].ToString();
e.DrawBackground();
e.Graphics.DrawString(txt, cbox.Font, System.Drawing.Brushes.Black, new RectangleF(e.Bounds.X + 2, e.Bounds.Y + 2, e.Bounds.Width, e.Bounds.Height));
e.Graphics.DrawLine(new Pen(Color.LightGray), e.Bounds.X, e.Bounds.Top + e.Bounds.Height - 1, e.Bounds.Width, e.Bounds.Top + e.Bounds.Height - 1);
e.DrawFocusRectangle();
}
private void comboBox2_SelectedIndexChanged(object sender, EventArgs e)
{
ComboxBoxEx cbox = (ComboxBoxEx)sender;
if (cbox.SelectedItem == null) return;
DataRowView item = (DataRowView)cbox.SelectedItem;
//label1.Text = item["id"].ToString();
}
}
}
The ComboBoxEx Class
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Collections.Generic;
namespace WindowsFormsApplication1
{
public partial class ComboxBoxEx : ComboBox
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
public const int SWP_NOZORDER = 0x0004;
public const int SWP_NOACTIVATE = 0x0010;
public const int SWP_FRAMECHANGED = 0x0020;
public const int SWP_NOOWNERZORDER = 0x0200;
public const int WM_CTLCOLORLISTBOX = 0x0134;
private int _hwndDropDown = 0;
internal List<int> ItemHeights = new List<int>();
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CTLCOLORLISTBOX)
{
if (_hwndDropDown == 0)
{
_hwndDropDown = m.LParam.ToInt32();
RECT r;
GetWindowRect((IntPtr)_hwndDropDown, out r);
int newHeight = 0;
int n = (Items.Count > MaxDropDownItems) ? MaxDropDownItems : Items.Count;
for (int i = 0; i < n; i++)
{
newHeight += ItemHeights[i];
}
newHeight += 5; //to stop scrollbars showing
SetWindowPos((IntPtr)_hwndDropDown, IntPtr.Zero,
r.Left,
r.Top,
DropDownWidth,
newHeight,
SWP_FRAMECHANGED |
SWP_NOACTIVATE |
SWP_NOZORDER |
SWP_NOOWNERZORDER);
}
}
base.WndProc(ref m);
}
protected override void OnDropDownClosed(EventArgs e)
{
_hwndDropDown = 0;
base.OnDropDownClosed(e);
}
}
}