Adding inputbox-like control to XNA game - c#

I want my game to have normal text input, but it seems very unpleasant to do using pure XNA.
Earlier I found this piece of code which lets me use MessageBox all around my game, safely pausing its execution and showing a message:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern uint MessageBox(IntPtr hWnd, String text, String caption, uint type);
Is there something similar to this which could add InputBox functionality to my game, preferrably without interrupting (pausing) the game?

Ah, the text input - I have very recent experience with this.
Problem
Usually, Keyboard.GetKeyboardState() sucks at getting text input, and that is for many reasons, some of them being:
You have to code a HUGE switch to detect what key had been pressed
You have to manually detect whether to capitalize letters (Shift or CapsLock)
You have to decipher those OemPeriod-like keys (as in test) to see where they actually are, and map them to specific values.
There is no way to detect/use keyboard layout or keyboard language
You have to implement own mechanism for timing repetition in case of key being held down
Second part of the problem is detecting which of your TextBoxes (or UI controls in general) is currently receiving this input, since you don't want all of your boxes to receive text as you type.
Third, you need to draw the TextBox in specified bounds, and you could also want to draw the caret (the blinking vertical position indicator), the current selection (if you want to go so far to implement it), the texture that represents the box, and the textures for highlighted (with mouse) or selected (has focus) state.
Fourth, you have to manually implement copy-paste features.
Quick note
You probably don't need all these features, as I didn't need them. You'd just want simple input, and detection for keys such as enter or tab, as well as mouse click. Maybe also paste.
Solution
The thing is (at least when we talk about Windows, not X-Box or WP7), the operating system already has the mechanisms necessary to implement everything you need from your keyboard:
Gives characters based on current keyboard layout and language
Automatically handles repeating input (in case of key being held down)
Automatically capitalizes and provides special characters
Solution I use for getting keyboard input, I've copied off this Gamedev.net forum post. It is the code below, and you just need to copy-paste it into a .cs file which you'll never have to open again.
It is used for receiving localized input from your keyboard, and all you need to do is initialize it in your Game.Initialize() override method (by using Game.Window), and hook up to the events to receive input anywhere you'd like.
You need to add PresentationCore (PresentationCore.dll) to your references in order to use this code (needed for System.Windows.Input namespace). This works for .NET 4.0 and for .NET 4.0 Client Profile.
EventInput
using System;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System.Text;
using System.Windows.Input;
namespace EventInput
{
public class KeyboardLayout
{
const uint KLF_ACTIVATE = 1; //activate the layout
const int KL_NAMELENGTH = 9; // length of the keyboard buffer
const string LANG_EN_US = "00000409";
const string LANG_HE_IL = "0001101A";
[DllImport("user32.dll")]
private static extern long LoadKeyboardLayout(
string pwszKLID, // input locale identifier
uint Flags // input locale identifier options
);
[DllImport("user32.dll")]
private static extern long GetKeyboardLayoutName(
System.Text.StringBuilder pwszKLID //[out] string that receives the name of the locale identifier
);
public static string getName()
{
System.Text.StringBuilder name = new System.Text.StringBuilder(KL_NAMELENGTH);
GetKeyboardLayoutName(name);
return name.ToString();
}
}
public class CharacterEventArgs : EventArgs
{
private readonly char character;
private readonly int lParam;
public CharacterEventArgs(char character, int lParam)
{
this.character = character;
this.lParam = lParam;
}
public char Character
{
get { return character; }
}
public int Param
{
get { return lParam; }
}
public int RepeatCount
{
get { return lParam & 0xffff; }
}
public bool ExtendedKey
{
get { return (lParam & (1 << 24)) > 0; }
}
public bool AltPressed
{
get { return (lParam & (1 << 29)) > 0; }
}
public bool PreviousState
{
get { return (lParam & (1 << 30)) > 0; }
}
public bool TransitionState
{
get { return (lParam & (1 << 31)) > 0; }
}
}
public class KeyEventArgs : EventArgs
{
private Keys keyCode;
public KeyEventArgs(Keys keyCode)
{
this.keyCode = keyCode;
}
public Keys KeyCode
{
get { return keyCode; }
}
}
public delegate void CharEnteredHandler(object sender, CharacterEventArgs e);
public delegate void KeyEventHandler(object sender, KeyEventArgs e);
public static class EventInput
{
/// <summary>
/// Event raised when a character has been entered.
/// </summary>
public static event CharEnteredHandler CharEntered;
/// <summary>
/// Event raised when a key has been pressed down. May fire multiple times due to keyboard repeat.
/// </summary>
public static event KeyEventHandler KeyDown;
/// <summary>
/// Event raised when a key has been released.
/// </summary>
public static event KeyEventHandler KeyUp;
delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
static bool initialized;
static IntPtr prevWndProc;
static WndProc hookProcDelegate;
static IntPtr hIMC;
//various Win32 constants that we need
const int GWL_WNDPROC = -4;
const int WM_KEYDOWN = 0x100;
const int WM_KEYUP = 0x101;
const int WM_CHAR = 0x102;
const int WM_IME_SETCONTEXT = 0x0281;
const int WM_INPUTLANGCHANGE = 0x51;
const int WM_GETDLGCODE = 0x87;
const int WM_IME_COMPOSITION = 0x10f;
const int DLGC_WANTALLKEYS = 4;
//Win32 functions that we're using
[DllImport("Imm32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr ImmGetContext(IntPtr hWnd);
[DllImport("Imm32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
/// <summary>
/// Initialize the TextInput with the given GameWindow.
/// </summary>
/// <param name="window">The XNA window to which text input should be linked.</param>
public static void Initialize(GameWindow window)
{
if (initialized)
throw new InvalidOperationException("TextInput.Initialize can only be called once!");
hookProcDelegate = new WndProc(HookProc);
prevWndProc = (IntPtr)SetWindowLong(window.Handle, GWL_WNDPROC,
(int)Marshal.GetFunctionPointerForDelegate(hookProcDelegate));
hIMC = ImmGetContext(window.Handle);
initialized = true;
}
static IntPtr HookProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
IntPtr returnCode = CallWindowProc(prevWndProc, hWnd, msg, wParam, lParam);
switch (msg)
{
case WM_GETDLGCODE:
returnCode = (IntPtr)(returnCode.ToInt32() | DLGC_WANTALLKEYS);
break;
case WM_KEYDOWN:
if (KeyDown != null)
KeyDown(null, new KeyEventArgs((Keys)wParam));
break;
case WM_KEYUP:
if (KeyUp != null)
KeyUp(null, new KeyEventArgs((Keys)wParam));
break;
case WM_CHAR:
if (CharEntered != null)
CharEntered(null, new CharacterEventArgs((char)wParam, lParam.ToInt32()));
break;
case WM_IME_SETCONTEXT:
if (wParam.ToInt32() == 1)
ImmAssociateContext(hWnd, hIMC);
break;
case WM_INPUTLANGCHANGE:
ImmAssociateContext(hWnd, hIMC);
returnCode = (IntPtr)1;
break;
}
return returnCode;
}
}
}
Now you could already use this as it is (by subscribing to EventInput.CharEntered event), and use logic to detect where to send your input.
KeyboardDispatcher, IKeyboardSubscriber
What I did was create a class KeyboardDispatcher, which handles the dispatching of keyboard input by way of having a property of type IKeyboardSubscriber to which it sends received input. The idea is that you set this property to that UI control that you want to receive input.
Definitions are as follows:
public interface IKeyboardSubscriber
{
void RecieveTextInput(char inputChar);
void RecieveTextInput(string text);
void RecieveCommandInput(char command);
void RecieveSpecialInput(Keys key);
bool Selected { get; set; } //or Focused
}
public class KeyboardDispatcher
{
public KeyboardDispatcher(GameWindow window)
{
EventInput.EventInput.Initialize(window);
EventInput.EventInput.CharEntered += new EventInput.CharEnteredHandler(EventInput_CharEntered);
EventInput.EventInput.KeyDown += new EventInput.KeyEventHandler(EventInput_KeyDown);
}
void EventInput_KeyDown(object sender, EventInput.KeyEventArgs e)
{
if (_subscriber == null)
return;
_subscriber.RecieveSpecialInput(e.KeyCode);
}
void EventInput_CharEntered(object sender, EventInput.CharacterEventArgs e)
{
if (_subscriber == null)
return;
if (char.IsControl(e.Character))
{
//ctrl-v
if (e.Character == 0x16)
{
//XNA runs in Multiple Thread Apartment state, which cannot recieve clipboard
Thread thread = new Thread(PasteThread);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
_subscriber.RecieveTextInput(_pasteResult);
}
else
{
_subscriber.RecieveCommandInput(e.Character);
}
}
else
{
_subscriber.RecieveTextInput(e.Character);
}
}
IKeyboardSubscriber _subscriber;
public IKeyboardSubscriber Subscriber
{
get { return _subscriber; }
set
{
if (_subscriber != null)
_subscriber.Selected = false;
_subscriber = value;
if(value!=null)
value.Selected = true;
}
}
//Thread has to be in Single Thread Apartment state in order to receive clipboard
string _pasteResult = "";
[STAThread]
void PasteThread()
{
if (Clipboard.ContainsText())
{
_pasteResult = Clipboard.GetText();
}
else
{
_pasteResult = "";
}
}
}
Usage is fairly simple, instantiate KeyboardDispatcher, i.e. in Game.Initialize() and keep a reference to it (so you can switch between selected [focused] controls), and pass it a class that uses the IKeyboardSubscriber interface, such as your TextBox.
TextBox
Next up is your actual control. Now I've originally programed a fairly complicated box that used render targets to render the text to a texture so I could move it around (if text was larger than the box), but then after a lot of pain i scrapped it and made a really simple version. Feel free to improve it!
public delegate void TextBoxEvent(TextBox sender);
public class TextBox : IKeyboardSubscriber
{
Texture2D _textBoxTexture;
Texture2D _caretTexture;
SpriteFont _font;
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; private set; }
public bool Highlighted { get; set; }
public bool PasswordBox { get; set; }
public event TextBoxEvent Clicked;
string _text = "";
public String Text
{
get
{
return _text;
}
set
{
_text = value;
if (_text == null)
_text = "";
if (_text != "")
{
//if you attempt to display a character that is not in your font
//you will get an exception, so we filter the characters
//remove the filtering if you're using a default character in your spritefont
String filtered = "";
foreach (char c in value)
{
if (_font.Characters.Contains(c))
filtered += c;
}
_text = filtered;
while (_font.MeasureString(_text).X > Width)
{
//to ensure that text cannot be larger than the box
_text = _text.Substring(0, _text.Length - 1);
}
}
}
}
public TextBox(Texture2D textBoxTexture, Texture2D caretTexture, SpriteFont font)
{
_textBoxTexture = textBoxTexture;
_caretTexture = caretTexture;
_font = font;
_previousMouse = Mouse.GetState();
}
MouseState _previousMouse;
public void Update(GameTime gameTime)
{
MouseState mouse = Mouse.GetState();
Point mousePoint = new Point(mouse.X, mouse.Y);
Rectangle position = new Rectangle(X, Y, Width, Height);
if (position.Contains(mousePoint))
{
Highlighted = true;
if (_previousMouse.LeftButton == ButtonState.Released && mouse.LeftButton == ButtonState.Pressed)
{
if (Clicked != null)
Clicked(this);
}
}
else
{
Highlighted = false;
}
}
public void Draw(SpriteBatch spriteBatch, GameTime gameTime)
{
bool caretVisible = true;
if ((gameTime.TotalGameTime.TotalMilliseconds % 1000) < 500)
caretVisible = false;
else
caretVisible = true;
String toDraw = Text;
if (PasswordBox)
{
toDraw = "";
for (int i = 0; i < Text.Length; i++)
toDraw += (char) 0x2022; //bullet character (make sure you include it in the font!!!!)
}
//my texture was split vertically in 2 parts, upper was unhighlighted, lower was highlighted version of the box
spriteBatch.Draw(_textBoxTexture, new Rectangle(X, Y, Width, Height), new Rectangle(0, Highlighted ? (_textBoxTexture.Height / 2) : 0, _textBoxTexture.Width, _textBoxTexture.Height / 2), Color.White);
Vector2 size = _font.MeasureString(toDraw);
if (caretVisible && Selected)
spriteBatch.Draw(_caretTexture, new Vector2(X + (int)size.X + 2, Y + 2), Color.White); //my caret texture was a simple vertical line, 4 pixels smaller than font size.Y
//shadow first, then the actual text
spriteBatch.DrawString(_font, toDraw, new Vector2(X, Y) + Vector2.One, Color.Black);
spriteBatch.DrawString(_font, toDraw, new Vector2(X, Y), Color.White);
}
public void RecieveTextInput(char inputChar)
{
Text = Text + inputChar;
}
public void RecieveTextInput(string text)
{
Text = Text + text;
}
public void RecieveCommandInput(char command)
{
switch (command)
{
case '\b': //backspace
if (Text.Length > 0)
Text = Text.Substring(0, Text.Length - 1);
break;
case '\r': //return
if (OnEnterPressed != null)
OnEnterPressed(this);
break;
case '\t': //tab
if (OnTabPressed != null)
OnTabPressed(this);
break;
default:
break;
}
}
public void RecieveSpecialInput(Keys key)
{
}
public event TextBoxEvent OnEnterPressed;
public event TextBoxEvent OnTabPressed;
public bool Selected
{
get;
set;
}
}
When you instantiate a TextBox, don't forget to set X, Y, and Width (!!!) values on the instance (Height is auto-set by font).
The texture I used for the box was (unhighlighted has a gradient, which looks nice on a black background :) )
To display the box call the .Draw() method on the instance (in your Game.Draw() method), with spritebatch already started (SpriteBatch.Begin() called!!!). For each box you're displaying, if you want it to recieve mouse input you should call .Update() method.
When you want a specific instance to recieve keyboard input, use your KeyboardDispatcher instance to subscribe it, such as:
_keyboardDispatcher.Subscriber = _usernameTextBox;
You can use the Click, Tab and Enter events on the textbox to switch subscribers (which I recommend as it gives a really nice feel to the UI when you can tab through it, and click to select).
Unresolved issues
Ofc, I had talked about some features I had not implemented, such as the box being able to pan the text if the text was wider than the box, the ability to move the caret around (insert the text, not just append), to select and copy text, etc.
These problems you could solve with a light to medium effort, I'm sure of it, but before you do, ask yourself:
Do I really need it?

Having written such code a few times, I'd say it's not that difficult to program a basic textbox in XNA. You define a rectangle that you fill with a background color, a string that represents what the user has typed, and display the string using Spritebatch.DrawString() inside the rectangle ! Using SpriteFont.MeasureString(), you can align the text however you want, wrap text to the next line when it's off-limits, etc.
Then you look at Keyboard.GetState() each update and check which keys have been pressed. That's maybe the biggest problem, because if the user types fast, you'll miss some keystrokes - the game only updates so many times per second. The problem is widely documented on the internet, and has solutions, for example here.
Another option would be to use a pre-made XNA GUI component, such as what you get with the Nuclex framework.

Well, the simplest way would be as follow (from my point of view ;])
using TextboxInputTest.Textbox.TextInput;
private TextboxInput _inputTextBox
then I'd recommend to enable mouse (set it visible)
IsMouseVisible = true;
now ya need to initialize the textBox itself
this._inputTextBox = new TextboxInput(this, "background_box", "Arial");
this stands for the game, which is this one (doubt you'd need to change that)
background_box is the name of picture ya wanna use to be displayed (afaik, there's no default option for this)
Arial is the font ya wanna use (don't forget you have to add it to the content of the game
Set the box's position
this._inputTextBox.Position = new Vector2(100,100);
And as the final step you have to add the box to the component array
Components.Add(this._inputTextBox);
There are many features you might like to edit, for that, i'd recommend to use IntelliSense
edit: my fault, sorry, i use them so commonly i completely forgot about that ;] saying in advance, what ya see bellow is not my work
http://www.4shared.com/file/RVqzHWk0/TextboxInput.html
Hope it helped.
Regards,
Releis

Related

How can I track and read/write Mouse movement in c# Console App

So I made a program that tracks mouse movement for a game called rust, I made this to track spray patterns in guns and etc. I made and it works except for it only tracks the position on screen and not actual movement and in a game like rust, the mouse is always centered so it's useless. I looked everywhere with every search title I could think of and couldn't find anything. what is the best way to track mouse movement that returns X and Y coordinates?
TL:DR
I made a program and it works but the wrong way, what is the best way to track mouse movement that returns X and Y coordinates?
here is the code if needed:
using System;
using Timer = System.Timers.Timer;
using System.Timers;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;
using Gma.System.MouseKeyHook;
using System.IO;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace MouseTrackerConsole
{
class Program
{
private static List<Coords> QuickCoords = new List<Coords>();
private static int fileNumber = 0;
private static Rectangle screenResolution = Screen.PrimaryScreen.Bounds;
private static IKeyboardMouseEvents HookEvents = null;
private static Timer loopTimer;
[STAThread]
static void Main(string[] args)
{
DirectoryInfo di = new DirectoryInfo("Spray");
foreach (FileInfo file in di.GetFiles())
{
file.Delete();
}
loopTimer = new Timer();
loopTimer.Interval = 100;
loopTimer.Enabled = false;
loopTimer.Elapsed += loopTimerEvent;
loopTimer.AutoReset = true;
if(!Directory.Exists("Spray"))
{
Directory.CreateDirectory("Spray");
}
Hook.GlobalEvents().MouseDown += async (sender, e) =>
{
if (e.Button == MouseButtons.Left)
{
Thread.Sleep(100);
loopTimer.Enabled = true;
}
};
Hook.GlobalEvents().MouseUp += async (sender, e) =>
{
if (e.Button == MouseButtons.Left)
{
loopTimer.Enabled = false;
if (QuickCoords.Count == 0)
{
}
else
{
using (StreamWriter file = File.CreateText($#"Spray\Spray{fileNumber}.akspray"))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(file, QuickCoords);
}
QuickCoords.Clear();
fileNumber += 1;
Console.WriteLine(fileNumber);
}
}
};
try
{
Application.Run(new ApplicationContext());
}
catch (AccessViolationException)
{
Environment.Exit(0);
}
}
private static void loopTimerEvent(Object source, ElapsedEventArgs e)
{
Console.WriteLine("x: " + Cursor.Position.X + " y: " + Cursor.Position.Y);
QuickCoords.Add(new Coords { X = Cursor.Position.X.ToString(), Y = Cursor.Position.Y.ToString() });
}
}
internal class Coords
{
public string X { get; set; }
public string Y { get; set; }
}
}
thank you in advance
It is possible to do using Pinvoke. The solution here is a start, but it may still be captured by the foreground application and never reported to your application.
If that's the case, you can use another winapi Pinvoke, SetWindowsHooksEx, like so:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, uint dwThreadId);
You'd still have to do some legwork setting up the winapi structs and the callback for the hook, but there's a pretty good example of how to set it up here: Using windows hooks with C# and P/Invoke
The important parts are:
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate IntPtr HookProc(int nCode, UIntPtr wParam, IntPtr lParam);
and
[StructLayout(LayoutKind.Sequential)]
internal struct MouseLowLevelHookStruct
{
public Point pt;
public int mouseData;
public int flags;
public int time;
public IntPtr dwExtraInfo;
}
Then it's a matter of setting up your hook with an appropriate callback and marshaling lParam to MouseLowLevelHookStruct.
Edit: I took a look at the MouseKeyHook nuget package you're using, and it seems behind the scenes it's calling the winapi methods I mentioned above. I couldn't get it to work appropriately from a console app, but in a winform app the following captured all mouse movement just fine:
Gma.System.MouseKeyHook.Hook.GlobalEvents().MouseMoveExt += Form1_MouseMoveExt;
It even reported attempts to move the mouse cursor off the screen as negative numbers, so it seems likely it will be able to report events from an application that keeps the mouse captive. Unless you have a very good reason to want to stick with a console app, it'd be easiest to just switch to winforms and use that event handler.

C# - How to display a checkbox when the user has scrolled to the bottom of a rich text box [duplicate]

I have searched the internet far and wide and seen many questions like this, but I have not seen an actual answer.
I have a rich text box control with lots of text in it. It has some legal information in this control. By default the "Accept" button is disabled. I want to detect on the scroll event if the position of the v-scroll bar is at the bottom. If it is at the bottom, enable the button.
How would I detect the current v-scroll bar position?
Thank You!
EDIT
I am using WinForms (.Net 4.0)
This should get you close to what you are looking for. This class inherits from the RichTextBox and uses some pinvoking to determine the scroll position. It adds an event ScrolledToBottom which gets fired if the user scrolls using the scrollbar or uses the keyboard.
public class RTFScrolledBottom : RichTextBox {
public event EventHandler ScrolledToBottom;
private const int WM_VSCROLL = 0x115;
private const int WM_MOUSEWHEEL = 0x20A;
private const int WM_USER = 0x400;
private const int SB_VERT = 1;
private const int EM_SETSCROLLPOS = WM_USER + 222;
private const int EM_GETSCROLLPOS = WM_USER + 221;
[DllImport("user32.dll")]
private static extern bool GetScrollRange(IntPtr hWnd, int nBar, out int lpMinPos, out int lpMaxPos);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, ref Point lParam);
public bool IsAtMaxScroll() {
int minScroll;
int maxScroll;
GetScrollRange(this.Handle, SB_VERT, out minScroll, out maxScroll);
Point rtfPoint = Point.Empty;
SendMessage(this.Handle, EM_GETSCROLLPOS, 0, ref rtfPoint);
return (rtfPoint.Y + this.ClientSize.Height >= maxScroll);
}
protected virtual void OnScrolledToBottom(EventArgs e) {
if (ScrolledToBottom != null)
ScrolledToBottom(this, e);
}
protected override void OnKeyUp(KeyEventArgs e) {
if (IsAtMaxScroll())
OnScrolledToBottom(EventArgs.Empty);
base.OnKeyUp(e);
}
protected override void WndProc(ref Message m) {
if (m.Msg == WM_VSCROLL || m.Msg == WM_MOUSEWHEEL) {
if (IsAtMaxScroll())
OnScrolledToBottom(EventArgs.Empty);
}
base.WndProc(ref m);
}
}
This is then how it can get used:
public Form1() {
InitializeComponent();
rtfScrolledBottom1.ScrolledToBottom += rtfScrolledBottom1_ScrolledToBottom;
}
private void rtfScrolledBottom1_ScrolledToBottom(object sender, EventArgs e) {
acceptButton.Enabled = true;
}
Tweak as necessary.
The following works very well in one of my solutions:
Point P = new Point(rtbDocument.Width, rtbDocument.Height);
int CharIndex = rtbDocument.GetCharIndexFromPosition(P);
if (rtbDocument.TextLength - 1 == CharIndex)
{
btnAccept.Enabled = true;
}
The question How to get scroll position for RichTextBox? could be helpful, Check out this function
richTextBox1.GetPositionFromCharIndex(0);

Why TextBox changed event triggering three times when type the value using korean keyboard?

I am using the korean language in my pc. If i type the value in TexBox Control TextBoxChanged event triggered three times. First time i got the typed value, second time i got the empty value at last i got the typed value.
If anyone know the reason for this.
Thanks in advance.
The reason is Korean IME will track key strokes and display the
intermediate character. So when we let richtextbox output its text in
TextChanged event, it will break IME's work
This is indeed due to a defect in the RichTextBox that causes IME entry mode to exit each time a character is completed, due to sending a WM_IME_COMPOSITION windows message to IME that causes it to think IME was completed. This is triggered when the Text property of the RichTextBox is retrieved.
Here is a workaround, which overrides the RichTextBox window procedure to intercept and avoid this erroneous code path for Korean IME entry, and uses an internal value for Text to avoid calling the underlying control's property if IME is mid-composition, but still update after each character is entered.
Note that the class exposes a bool property called KoreanWorkaroundEnabled, which is true by default. You can set this to false to revert the behavior to the default, which will be necessary if your text input language is Chinese or Japanese. In those cases, this workaround will break the normal behavior. You can use the InputLanguageChanged event on the host form to set the property according to the current entry language:
public Form1()
{
InitializeComponent();
this.InputLanguageChanged += Form1_InputLanguageChanged;
}
void Form1_InputLanguageChanged(object sender, InputLanguageChangedEventArgs e)
{
if (!e.InputLanguage.Culture.TwoLetterISOLanguageName.Equals("ko"))
richTextBoxKorean1.KoreanWorkaroundEnabled = false;
else
richTextBoxKorean1.KoreanWorkaroundEnabled = true;
}
Here is the overridden RichTextBox class that implements the
workaround:
public class RichTextBoxKorean : RichTextBox
{
[DllImport("imm32.dll", CharSet = CharSet.Unicode)]
private static extern int ImmGetCompositionString(IntPtr hIMC, uint dwIndex, byte[] lpBuf, int dwBufLen);
[DllImport("imm32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr ImmGetContext(IntPtr hWnd);
[DllImport("imm32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr ImmReleaseContext(IntPtr hWnd, IntPtr context);
public enum WM_IME
{
GCS_RESULTSTR = 0x800,
EM_STREAMOUT = 0x044A,
WM_IME_COMPOSITION =0x10F,
WM_IME_ENDCOMPOSITION =0x10E,
WM_IME_STARTCOMPOSITION =0x10D
}
private bool skipImeComposition = false;
private bool imeComposing = false;
public bool KoreanWorkaroundEnabled = true;
string _mText = "";
protected override void WndProc(ref Message m)
{
if (KoreanWorkaroundEnabled)
{
switch (m.Msg)
{
case (int)WM_IME.EM_STREAMOUT:
if (imeComposing)
{
skipImeComposition = true;
}
base.WndProc(ref m);
break;
case (int)WM_IME.WM_IME_COMPOSITION:
if (m.LParam.ToInt32() == (int)WM_IME.GCS_RESULTSTR)
{
IntPtr hImm = ImmGetContext(this.Handle);
int dwSize = ImmGetCompositionString(hImm, (int)WM_IME.GCS_RESULTSTR, null, 0);
byte[] outstr = new byte[dwSize];
ImmGetCompositionString(hImm, (int)WM_IME.GCS_RESULTSTR, outstr, dwSize);
_mText += Encoding.Unicode.GetString(outstr).ToString();
ImmReleaseContext(this.Handle, hImm);
}
if (skipImeComposition)
{
skipImeComposition = false;
break;
}
base.WndProc(ref m);
break;
case (int)WM_IME.WM_IME_STARTCOMPOSITION:
imeComposing = true;
base.WndProc(ref m);
break;
case (int)WM_IME.WM_IME_ENDCOMPOSITION:
imeComposing = false;
base.WndProc(ref m);
break;
default:
base.WndProc(ref m);
break;
}
}
else
base.WndProc(ref m);
}
public override string Text
{
get
{
if (!imeComposing)
{
_mText = base.Text;
return base.Text;
}
else
{
return _mText;
}
}
set
{
base.Text = value;
_mText = value;
}
}
}
https://social.msdn.microsoft.com/Forums/windows/en-US/cefa5376-7912-47f6-b86a-197a211c2b70/get-text-from-richtextbox-control-without-disturbing-ime-when-input-east-asian-language?forum=winforms

Get the value of a scrollbar's scroll for a Winforms Text Box (C#)

I am creating a Windows Form Application using C#
I require a checkbox to be greyed out until the user scrolls to the bottom of a text box.
How can I get the value of the textbox's scrollbar position?
This should be a RichTextBox so you can use its SelectionProtected property to ensure that the user cannot change the text. It does not have a Scroll event but that can be added by overriding WndProc() and detecting the WM_VSCROLL message. Checking if the last line is visible like #TaW does is not reliable unless the WordWrap property is set to False. Easier to just check the state of the scrollbar.
Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto your form. Subscribe the LicenseViewed event and set the checkbox' Enabled property to true. I would be remiss if I did not point out that only lawyers ever think this is a good idea, users find these kind of text boxes universally annoying and hate them with a passion. You only have one chance to create a good first impression.
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class LicenseBox : RichTextBox {
public event EventHandler LicenseViewed;
public override string Text {
get { return base.Text; }
set { base.Text = value; textChanged(); }
}
public new string Rtf {
get { return base.Rtf; }
set { base.Rtf = value; textChanged(); }
}
private bool eventFired;
private void textChanged() {
this.SelectAll();
this.SelectionProtected = true;
this.SelectionStart = this.SelectionLength = 0;
eventFired = false;
checkScrollbar();
}
private void checkScrollbar() {
if (eventFired || !this.IsHandleCreated) return;
var pos = new ScrollInfo();
pos.cbSize = Marshal.SizeOf(pos);
pos.fMask = 7;
if (!GetScrollInfo(this.Handle, SB_VERT, ref pos)) return;
if (pos.nPos >= pos.nMax - pos.nPage) {
if (LicenseViewed != null) LicenseViewed(this, EventArgs.Empty);
eventFired = true;
}
}
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
if (m.Msg == WM_VSCROLL || m.Msg == WM_MOUSEWHEEL) checkScrollbar();
}
// Pinvoke
private const int WM_VSCROLL = 0x115;
private const int WM_MOUSEWHEEL = 0x20A;
private const int SB_VERT = 1;
private struct ScrollInfo {
public int cbSize, fMask, nMin, nMax, nPage, nPos, nTrackPos;
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool GetScrollInfo(IntPtr hwnd, int bar, ref ScrollInfo pos);
}
Here is a function that tells you if the last line is visible:
bool LastLineVisible(TextBox textbox)
{
Point lowPoint = new Point(3, textbox.ClientSize.Height - 3);
int lastline = textbox.Lines.Count() - 1;
int charOnLastvisibleLine = textbox.GetCharIndexFromPosition(lowPoint);
int lastVisibleLine = textbox.GetLineFromCharIndex(charOnLastvisibleLine);
return lastVisibleLine >= lastline;
}
You will still need to detect the scrolling event itself. See here on how to detect the scrolling.

Is there a way to auto scroll a listbox in C# WinForms

I an writing a program will display a list of number in serveral list boxes on a big monitor, my question is is there a way to auto scroll the list box to show all data in the boxes?
Normally, I would do:
listBox.SelectedIndex = listBox.Items.Count - 1;
listBox.SelectedIndex = -1;
but you can also try
int nItems = (int)(listBox.Height / listBox.ItemHeight);
listBox.TopIndex = listBox.Items.Count - nItems;
Hope this helps :)
To directly control the scrolling without selecting items, you need to use the Win32 SetScrollPos method from User32.dll. Here is a extended class which gives you basic support:
public class ScrollableListView : ListView
{
private const int WM_VSCROLL = 0x115;
private enum ScrollBar : int { Horizontal = 0x0, Vertical = 0x1 }
public void SetScroll(int x, int y)
{
this.SetScroll(ScrollBar.Horizontal, x);
this.SetScroll(ScrollBar.Vertical, y);
}
public void SetScrollX(int position)
{
this.SetScroll(ScrollBar.Horizontal, position);
}
public void SetScrollY(int position)
{
this.SetScroll(ScrollBar.Vertical, position);
}
[DllImport("User32.Dll", EntryPoint = "PostMessageA")]
private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
[DllImport("user32.dll")]
private static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
private void SetScroll(ScrollBar bar, int position)
{
if (!this.IsDisposed)
{
ScrollableListView.SetScrollPos((IntPtr)this.Handle, (int)bar, position, true);
ScrollableListView.PostMessage((IntPtr)this.Handle, ScrollableListView.WM_VSCROLL, 4 + 0x10000 * position, 0);
}
}
}
You can then set the X or Y scroll quickly and easily. This should work for other controls too.
If you want to make the control automatically scroll down and up, you would need to set up a recurring timer, with an interval of around 20 milliseconds. Keep track of the scroll position and direction, and increment or decrement it accordingly, sending the position to the control using these methods.
UPDATE:
I had a few problems with the SetScrollPos method posted above, primarily around the scrollbar moving, but the contents not. It's probably just a minor oversight, but in the meantime, here is a somewhat "outside the box" MarqueeListView solution..
First up, the enum representing which scrollbar to use. I used display names instead of the Win32 names for these (SB_HORIZ and SB_VERT) just to make things a little clearer.
public enum ScrollBarDirection : int { Horizontal = 0x0, Vertical = 0x1 }
Another enum for the scroll command codes themselves - I've trimmed out everything apart from Up (SB_LINEUP), Down (SB_LINEDOWN) and EndScroll (SB_ENDSCROLL). EndScroll is needed after the scroll message to inform the control to update.
public enum ScrollCommand : int { Up = 0x0, Down = 0x1, EndScroll = 0x8 }
Then finally the class itself. It basically starts by scrolling down, every 20ms (by default - note this is changeable via the MarqueeSpeed property). It then gets the scroll position, and compares it to last time. Once the scroll bar stops moving, it reverses the direction. This is to get around problems I was having with the GetScrollInfo method.
public class MarqueeListView : ListView
{
protected const int WM_VSCROLL = 0x115;
private ScrollCommand scrollCommand;
private int scrollPositionOld;
private Timer timer;
public MarqueeListView()
: base()
{
this.MarqueeSpeed = 20;
this.scrollPositionOld = int.MinValue;
this.scrollCommand = ScrollCommand.Down;
this.timer = new Timer() { Interval = this.MarqueeSpeed };
this.timer.Tick += (sender, e) =>
{
int scrollPosition = MarqueeListView.GetScrollPos((IntPtr)this.Handle, (int)ScrollBarDirection.Vertical);
if (scrollPosition == this.scrollPositionOld)
{
if (this.scrollCommand == ScrollCommand.Down)
{
this.scrollCommand = ScrollCommand.Up;
}
else
{
this.scrollCommand = ScrollCommand.Down;
}
}
this.scrollPositionOld = scrollPosition;
MarqueeListView.SendMessage((IntPtr)this.Handle, MarqueeListView.WM_VSCROLL, (IntPtr)this.scrollCommand, IntPtr.Zero);
MarqueeListView.SendMessage((IntPtr)this.Handle, MarqueeListView.WM_VSCROLL, (IntPtr)ScrollCommand.EndScroll, IntPtr.Zero);
};
this.timer.Start();
}
public int MarqueeSpeed
{
get
{
return this.timer.Interval;
}
set
{
this.timer.Interval = value;
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
protected static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
}
Finally, here's a quick Main method to test it:
private static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Form form = new Form() { StartPosition = FormStartPosition.CenterScreen, Width = 1280, Height = 720 };
MarqueeListView list = new MarqueeListView() { View = View.Tile, Dock = DockStyle.Fill };
for (int i = 0; i < 1000; i++) { list.Items.Add(Guid.NewGuid().ToString()); }
form.Controls.Add(list);
Application.Run(form);
}
Please bear in mind, this isn't necessarily the "proper" or best way of doing things, but I figured a different approach might give you some ideas!
I was hoping to use SetScrollPos which would give a much better, smoother effect. You can then easily include acceleration and deceleration - possibly optionally slowing to a halt on mouse over, then accelerating away on mouse out etc. It's just not playing ball at present though - I've got a working scroll update method in an old project somewhere, so I'll update this if I get it working again.
Hope that helps!
Even if it was a long time ago, others will certainly have the same problem. Since selecting the last item did not lead to automatic scrolling for me, here is a solution that works for me:
ListBox1.ScrollIntoView(ListBox1.Items[ListBox1.Items.Count - 1]);
Or just insert your items at the top by using:
lbLog.Items.Insert(0,"LogItem");

Categories

Resources