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.
Related
I have added a DateTimePicker in ContextMenuStrip using ToolStripControlHost. I am using below code.
DateTimePicker startDate = new DateTimePicker();
(cms.Items["mnuStartDate"] as ToolStripMenuItem).DropDownItems.Add(new ToolStripControlHost(startDate));
Selecting of date is working but when I clicked on the arrows to go to the previous or next month, or if I clicked on the Month, the ToolStrip is closing. Is there any workaround here? Thanks.
Note: I am showing the cms(ContextMenuStrip ) when Right Clicking on a control.
Looks like the ToolStrip behavior closes when an item is selected. So I juts used CalendarMonth Control as a replacement. Link here
It seems to be related to Application.EnableVisualStyles(); When not-enabled, the menu doesn't unexpectedly close.
When enabled, to prevent closing, detect if the CalendarMonth menu is currently visible, and if the mouse was clicked on it.
public class DatePicker : DateTimePicker {
private const int DtmFirst = 0x1000;
private const int DtmGetmonthcal = (DtmFirst + 8);
private bool isDroppedDown = false;
private IntPtr hwndCalendarWindow = IntPtr.Zero;
protected override void OnDropDown(EventArgs e) {
isDroppedDown = true;
hwndCalendarWindow = SendMessage(Handle, DtmGetmonthcal, 0, 0);
base.OnDropDown(e);
}
protected override void OnCloseUp(EventArgs eventargs) {
isDroppedDown = false;
base.OnCloseUp(eventargs);
}
public bool CalendarMonthClicked(Point pt) {
if (!isDroppedDown)
return false;
RECT r = new RECT();
GetWindowRect(hwndCalendarWindow, out r);
return pt.X >= r.Left && pt.X <= r.Right && pt.Y >= r.Top && pt.Y <= r.Bottom;
}
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("User32.dll")]
private static extern IntPtr SendMessage(IntPtr h, int msg, int param, int data);
[StructLayout(LayoutKind.Sequential)]
private struct RECT {
public int Left, Top, Right, Bottom;
}
}
static class Program {
//[DllImport("uxtheme.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
//public extern static Int32 SetWindowTheme(IntPtr hWnd, String textSubAppName, String textSubIdList);
[STAThread]
static void Main() {
Application.EnableVisualStyles(); // if you disable visual styles then the menu won't close
DatePicker dpStart = new DatePicker();
dpStart.MinimumSize = new Size(100, 50);
Form ff = new Form();
ContextMenuStrip cms = new ContextMenuStrip();
ToolStripMenuItem menu = new ToolStripMenuItem("Menu");
menu.DropDown.Closing += (o, e) => {
e.Cancel = (e.CloseReason == ToolStripDropDownCloseReason.AppClicked && dpStart.CalendarMonthClicked(Cursor.Position));
};
// sub-menu doesn't close properly, probably related to the ToolStripControlHost
cms.Opening += delegate {
menu.DropDown.Close(ToolStripDropDownCloseReason.CloseCalled);
};
cms.Closed += delegate {
menu.DropDown.Close(ToolStripDropDownCloseReason.CloseCalled);
};
ff.ContextMenuStrip = cms;
cms.Items.Add(menu);
ToolStripControlHost item = new ToolStripControlHost(dpStart) { AutoSize = true };
menu.DropDownItems.Add(item);
Application.Run(ff);
}
}
I am working with win forms. I have two forms : my main form, Form1 and a Form I made called TextBlock. I am making a text editor, where you can place text boxes around a page, and edit them (think word).
Here are my two forms.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
saveFileDialog.Filter = "Text File|*.txt";
var result = saveFileDialog.ShowDialog();
if (result == DialogResult.OK)
{
StreamWriter writer = new
StreamWriter(saveFileDialog.OpenFile());
writer.Write(tb_Main.Text);
writer.Dispose();
writer.Close();
}
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
}
private void button_tb_Click(object sender, EventArgs e)
{
TextBlock tb_edit = new TextBlock();
tb_edit.Text = "New Text Block";
// tb_edit.Multiline = true;
// tb_edit.Size = new Size(100,100);
// tb_edit.MinimumSize = new Size(50, 50);
tb_edit.form1 = this;
tb_edit.TopLevel = false;
tb_edit.btn_accepttb.BringToFront();
tb_Main.Controls.Add(tb_edit);
tb_edit.Show();
tb_edit.BringToFront();
}
}
and my custom form here:
public partial class TextBlock : Form
{
public Form1 form1;
public TextBlock()
{
InitializeComponent();
}
private void btn_accepttb_Click(object sender, EventArgs e)
{
TextBox tb_edit = new TextBox();
tb_edit.Text = "New Text Block";
tb_edit.Multiline = true;
tb_edit.Size = this.Size;
int dif = form1.tb_Main.Lines.Count()*(int)tb_edit.Font.Size;
Point loca = new Point(this.Location.X,this.Location.Y+dif);
tb_edit.Location = this.Location;
form1.tb_Main.Controls.Add(tb_edit);
tb_edit.Show();
tb_edit.BringToFront();
form1.tb_Main.Controls.Remove(this);
}
}
What it does: it makes a copy my TextBlock, for placement and resizing purposes. when you have it where you want and how big you want, you click the button, and it replaces itself with a regular textbox, of that size, in that position.
What I want it to do: currently it is working with one exception. I am adding it to the controls, of tb_Main (my main textbox, it takes up the whole form) in Form1, it shows up. its the right size, except when i fill tb_Main with text, and i scroll, the new textbox stays where it is while its parent scrolls behind it.
Question : if I scroll down into my document and decide I want this textbox here, how do I ensure its position relative to the scroll of the textbox im putting it in. so when I scroll on, it basically stays embedded in the page where I put it (when I say "page" im referring to my tb_Main).
It is better to create a richtextbox instead of textbox. It has all capabilities of textbox and way more. This example works only with a richtextbox.
Subclassed richtextbox:
class MyRichTextBox : RichTextBox {
[DllImport( "user32.dll" )]
private static extern bool GetScrollInfo( IntPtr hwnd, SBOrientation fnBar,
ref SCROLLINFO lpsi );
[StructLayout( LayoutKind.Sequential )]
private struct SCROLLINFO {
public uint cbSize;
public ScrollInfoMask fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
}
private enum ScrollInfoMask : uint {
SIF_RANGE = 0x1,
SIF_PAGE = 0x2,
SIF_POS = 0x4,
SIF_DISABLENOSCROLL = 0x8,
SIF_TRACKPOS = 0x10,
SIF_ALL = ( SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS ),
}
private enum SBOrientation : int {
SB_HORZ = 0x0,
SB_VERT = 0x1,
}
private const int WM_VSCROLL = 0x115;
private const int SB_LINEUP = 0;
private const int SB_LINEDOWN = 1;
private const int SB_PAGEUP = 2;
private const int SB_PAGEDOWN = 3;
private const int SB_THUMBPOSITION = 4;
private const int SB_THUMBTRACK = 5;
private const int SB_TOP = 6;
private const int SB_BOTTOM = 7;
private const int SB_ENDSCROLL = 8;
private bool isThumbTrack = false;
private TextBox childTextbox = null;
private int scrollPos = 0;
public MyTextBox( TextBox textbox ) { //here you pass the child textbox you want to scroll
childTextbox = textbox;
}
protected override void OnVScroll( EventArgs e ) {
if( childTextbox == null ) { base.OnVScroll( e ); return; }
SCROLLINFO si = new SCROLLINFO();
si.cbSize = (uint)Marshal.SizeOf( si );
si.fMask = ScrollInfoMask.SIF_ALL;
GetScrollInfo( this.Handle, SBOrientation.SB_VERT, ref si );
//there is a difference when the user uses the thumb to get scroll pos.
//When user uses the thumb we get *nTrackPos* otherwise *nPos*
if( isThumbTrack == true ) {
childTextbox.Location = new Point( childTextbox.Location.X, childTextbox.Location.Y -
(si.nTrackPos - scrollPos ) );
isThumbTrack = false;
scrollPos = si.nTrackPos;
}
else {
childTextbox.Location = new Point( childTextbox.Location.X, childTextbox.Location.Y -
( si.nPos - scrollPos ) );
scrollPos = si.nPos;
}
base.OnVScroll( e );
}
protected override void WndProc( ref Message m ) {
int wParam;
if(m.Msg == WM_VSCROLL ) {
wParam = m.WParam.ToInt32();
wParam &= 0xFFFF; //get low 16 bits of wParam
//Check if user is using the thumb to scroll
if( wParam == SB_THUMBTRACK ) {
isThumbTrack = true;
}
}
base.WndProc( ref m );
}
}
I would like to load the icons from multiple files dragged onto a listview and some of the information of those files, but the process is going very slow.
In my test, a list of 381 files dragged (some are not exe so those are skipped in my code), takes over 2 minutes to load the icon from the file, and add them to the listview.
Following is condensed version of my code :
public Form1()
{
InitializeComponent();
listView1.DragEnter += ListView1_DragEnter;
listView1.DragDrop += ListView1_DragDrop;
listView1.LargeImageList = new ImageList() { ImageSize = new Size( 64, 64) };
listView1.SmallImageList = new ImageList();
listView1.AllowDrop = true;
}
private void ListView1_DragDrop(object sender, DragEventArgs e)
{
if(e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] values = (string[])e.Data.GetData(DataFormats.FileDrop);
Convert.IconExtractor i = new Convert.IconExtractor();
foreach (var v in values)
{
var info = new FileInfo(v);
if(info.Extension.ToLower() == ".exe")
{
ListViewItem item = new ListViewItem();
listView1.LargeImageList.Images.Add(i.Extract(info.FullName, Convert.IconSize.Large));
listView1.SmallImageList.Images.Add(i.Extract(info.FullName, Convert.IconSize.Small));
item.Text = Path.GetFileNameWithoutExtension(info.FullName);
item.ImageIndex = listView1.SmallImageList.Images.Count -1;
item.Tag = info.FullName;
listView1.Items.Add(item);
}
}
listView1.Refresh();
}
}
private void ListView1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.All;
}
}
The method used for extraction is pasted here for convenience :
public enum IconSize
{
Small,
Large
}
public class IconExtractor
{
//----------------------------------------------------------------------------
//
// Description: Extracts the icon associated with any file on your system.
// Author: WidgetMan http://softwidgets.com
//
// Remarks...
//
// Class requires the IconSize enumeration that is implemented in this
// same file. For best results, draw an icon from within a control's Paint
// event via the e.Graphics.DrawIcon method.
//
//----------------------------------------------------------------------------
private const int SHGFI_ICON = 0x100;
private const int SHGFI_SMALLICON = 0x1;
private const int SHGFI_LARGEICON = 0x0;
private struct SHFILEINFO
{
public IntPtr hIcon;
public int iIcon;
public int dwAttributes;
[VBFixedString(260), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[VBFixedString(80), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
}
[DllImport("shell32", EntryPoint = "SHGetFileInfoA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, int ByValcbFileInfo, int uFlags);
public IconExtractor()
{
}
public System.Drawing.Icon Extract(string File, IconSize Size)
{
SHFILEINFO aSHFileInfo = default(SHFILEINFO);
int cbFileInfo = 0;
int uflags = 0;
System.Drawing.Icon Icon = default(System.Drawing.Icon);
switch (Size)
{
case IconSize.Large:
uflags = SHGFI_ICON | SHGFI_LARGEICON;
break;
default:
uflags = SHGFI_ICON | SHGFI_SMALLICON;
break;
}
cbFileInfo = Marshal.SizeOf(aSHFileInfo);
SHGetFileInfo(File, 0, ref aSHFileInfo, cbFileInfo, uflags);
Icon = System.Drawing.Icon.FromHandle(aSHFileInfo.hIcon);
return Icon;
}
public System.Drawing.Icon Extract(string File)
{
return this.Extract(File, IconSize.Small);
}
}
}
What can I do to make this process quick. The concept is to make a quick launcher for multiple applications.
Also of note, while this process is happening, the drag-collection icon is still 'hung' on windows explorer until the drag & drop task has completed fully (looped through all the files).
Here is a rough draft to give a visual of the application:
(yes, i know the icons extracted look like crap as well, but I think that is a separate issue from the slow issue I am having)
My advice is not to load all 381 icons in one step, particularly if the items are not visible. Load them on demand as items are scrolled into view, of if the view is large enough, load them in the background using your threading/task/concurrency technology of choice.
That's what Windows does.
To speed up loading, you may want to use the System Image List which would most likely benefit from caching. Here's some code for getting the large icon. Just change size to be SHGFI_ICON for your use.
public static Icon GetLargeIcon(string FileName, bool jumbo, bool useFileAttributes=false)
{
var shinfo = new SHFILEINFO();
uint flags;
flags = SHGFI_SYSICONINDEX;
if (useFileAttributes)
{
flags |= SHGFI_USEFILEATTRIBUTES;
}
var res = SHGetFileInfo(FileName, FILE_ATTRIBUTE_NORMAL, ref shinfo, (uint) Marshal.SizeOf(shinfo), flags);
if (res == IntPtr.Zero)
{
throw (new FileNotFoundException());
}
var iconIndex = shinfo.iIcon;
// Get the System IImageList object from the Shell:
var iidImageList = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
IImageList iml;
var size = jumbo
? SHIL_JUMBO
: SHIL_EXTRALARGE;
var hres = SHGetImageList(size, iidImageList, out iml);
if (hres != 0)
{
throw (new Exception("Error SHGetImageList"));
}
IntPtr hIcon;
const int ILD_TRANSPARENT = 1;
hres = iml.GetIcon(iconIndex, ILD_TRANSPARENT, out hIcon);
var icon = Icon.FromHandle(hIcon);
icon = icon.Clone() as Icon;
var bm = icon.ToBitmap();
DestroyIcon(hIcon);
return icon;
}
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);
}
}
}
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();
}
}