Windows Forms Desktop Application Bar Not Responding - c#

I would have given some indication as to the actual issue in the title, but I can't figure out what it is. All I can state is that the form, when implementing the appbar, no longer responds after I attempt to assign a value to a variable, thereby causing me to have to stop debugging and restart the machine to regain the desktop working area. The location of the error is noted in the code below, and I have only listed the code to the point of the error. Is there anything blatantly obvious here that I do not see?
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
public partial class Form1 : AppBar
{
public Form1() : base()
{
InitializeComponent();
}
//This is a button on the test form.
//When clicked, the form should become a desktop application bar
//docked to the top of the desktop.
private void t_Click(object sender, EventArgs e)
{
base.SET_APPBAR();
}
}
public class AppBar : Form
{
protected internal Size appbarSize;
protected internal Point appbarLocation;
protected internal bool appbarMode;
private EDGES appbarEdge;
private RECT appbarRect;
private uint ID { get; private set; }
private IntPtr HANDLE { get; private set; }
//constructor
public AppBar() : base () //no issues here
{
appbarEdge = EDGES.ABE_TOP;
appbarRect = new RECT();
ID = 0;
HANDLE = this.Handle;
appbarMode = false;
}
protected internal void SET_APPBAR()
{
if (IS_NEW())
{
QUERY_AND_SET_POSITION(ref appbarRect); //Never get to here
}
}
private bool IS_NEW()
{
if (appbarMode) //so far, so good
{
return false;
}
while (ID == 0)
{
CREATE_ID(); //ID is created, I have verified this.
}
this.FormBorderStyle = FormBorderStyle.FixedToolWindow; //BorderStyle does change
appbarSize = this.Size; //Size is correct
appbarLocation = this.Location; //still no issues
NEW(); //this is where the error begins (see code further down)
return appbarMode; //Never get here
}
private void CREATE_ID()
{
ID = Hooks.RegisterWindowMessage(Guid.NewGuid().ToString());
}
private void QUERY_AND_SET_POSITION(ref RECT appbarRect)
{
SET_APPBAR_SIZE(ref appbarRect);
QUERY_POS(ref appbarRect);
APPBAR_RESIZE(ref appbarRect);
SET_POS(ref appbarRect);
this.Location = new Point(appbarRect.left, appbarRect.top);
appbarSize = new Size(appbarRect.right - appbarRect.left, appbarRect.bottom - appbarRect.top);
this.Size = appbarSize;
}
private void SET_APPBAR_SIZE(ref RECT appbarRect)
{
switch (appbarEdge)
{
case (EDGES.ABE_BOTTOM):
appbarRect.left = 0;
appbarRect.right = SystemInformation.PrimaryMonitorSize.Width;
appbarRect.top = SystemInformation.PrimaryMonitorSize.Height - appbarSize.Height;
appbarRect.bottom = SystemInformation.PrimaryMonitorSize.Height;
break;
case (EDGES.ABE_LEFT):
appbarRect.left = 0;
appbarRect.right = appbarSize.Width;
appbarRect.top = 0;
appbarRect.bottom = SystemInformation.PrimaryMonitorSize.Height;
break;
case (EDGES.ABE_RIGHT):
appbarRect.left = SystemInformation.PrimaryMonitorSize.Width - appbarSize.Width;
appbarRect.right = SystemInformation.PrimaryMonitorSize.Width;
appbarRect.top = 0;
appbarRect.bottom = SystemInformation.PrimaryMonitorSize.Height;
break;
default:
appbarRect.left = 0;
appbarRect.right = SystemInformation.PrimaryMonitorSize.Width;
appbarRect.top = SystemInformation.WorkingArea.Top;
appbarRect.bottom = appbarSize.Height;
break;
}
}
private void APPBAR_RESIZE(ref RECT appbarRect)
{
switch (appbarEdge)
{
case (EDGES.ABE_TOP):
appbarRect.bottom = appbarRect.top + appbarSize.Height;
break;
case (EDGES.ABE_BOTTOM):
appbarRect.top = appbarRect.bottom - appbarSize.Height;
break;
case (EDGES.ABE_LEFT):
appbarRect.right = appbarRect.left + appbarSize.Width;
break;
case (EDGES.ABE_RIGHT):
appbarRect.left = appbarRect.right - appbarSize.Width;
break;
}
}
private void APPBAR_CALLBACK(ref Message apiMessage)
{
switch ((NOTIFICATIONS)(uint)apiMessage.WParam)//? on int vs uint here
{
case (NOTIFICATIONS.ABN_STATECHANGE):
STATE_CHANGE();
break;
case (NOTIFICATIONS.ABN_POSCHANGED):
QUERY_AND_SET_POSITION(ref appbarRect);
break;
case (NOTIFICATIONS.ABN_FULLSCREENAPP):
if ((int)apiMessage.LParam != 0)
{
this.SendToBack();
this.TopMost = false;
}
else
{
STATE_CHANGE();
}
break;
case (NOTIFICATIONS.ABN_WINDOWARRANGE):
//first call
if ((int)apiMessage.LParam != 0)
{
this.Visible = false;
}
//second call
else
{
this.Visible = true;
}
break;
}
}
protected override void WndProc(ref Message apiMessage)
{
if (appbarMode)
{
if (HANDLE == apiMessage.HWnd)
{
APPBAR_CALLBACK(ref apiMessage);
}
else if (apiMessage.Msg == (int)API_MESSAGES.WM_ACTIVATE)
{
ACTIVATE();
}
else if (apiMessage.Msg == (int)API_MESSAGES.WM_WINDOWPOSCHANGED)
{
WINDOW_POS_CHANGED();
}
}
base.WndProc(ref apiMessage);
}
private void NEW()
{
APPBARDATA data = new APPBARDATA(); //no apparent issue
data.cbSize = (uint)Marshal.SizeOf(data); //no apparent issue
data.hWnd = HANDLE; //no apparent issue
data.uCallbackMessage = ID; //no apparent issue
if (Hooks.SHAppBarMessage((uint)DWORD.ABM_NEW, ref data) != 0) //SHAppBar returns 1 (true)
{
//no issue if I were to place a MessageBox here
appbarMode = true; // why in the world is this causing an issue?????
//can't do anything from this point
}
}
}
public static class Hooks
{
[DllImport("shell32.dll", EntryPoint = "SHAppBarMessage")]
public static extern uint SHAppBarMessage(uint dwMessage, ref APPBARDATA pData);
[DllImport("user32.dll", EntryPoint = "RegisterWindowMessage")]
public static extern uint RegisterWindowMessage(
[MarshalAs(UnmanagedType.LPTStr)]
string lpString);
}
After whatever the issue is occurs, I can't click on any button or close the form. Desktop working area is always appropriately resized. Hopefully all of this makes sense to someone. Thanks for looking in advance.

The problem is that you're performing a long running operation in the UI thread. This is blocking the UI thread and preventing it from doing anything else (from painting changes, responding to button click or mouse move events, etc.).
You need to perform your long running operation(s) in a background thread. While you can do this manually, using a BackgroundWorker is preferable as it is designed for exactly this application. Not only will it create/configure the background thread for you, but it provides simple and easy to use mechanisms for updating the UI when your background task is completed, updating the UI with progress while the task is working, etc.
Note that while in a background thread you cannot access UI elements; they need to be accessed from the UI thread. You should move all code that gets information from the UI to before you start the background task (saving it for later in local variables or fields) and you should move all code to update the UI based on the results to the end, (in the RunWorkerCompleted event, if using a BackgroundWorker). All of the events other than DoWork for the BackgroundWorker are all executed in the UI thread, and so can access your UI Controls.

Related

WPF ProgressBar update from another Class

The premise is really simple - I have a class that is running some expensive computation and I would like to show a ProgressBar to inform the user. Since I might have many computation cycles, I want to have a simple ProgressBar form that I can use many times in my code. That form does not need to know anything else but it's title, the maximum value for the ProgressBar and when to perform a step and increment. The update call is done from the Other Class, the form just displays that.
The following code is from a Windows Forms version of a ProgressBar (exported as a dll) that achieves the goal. I am trying to recreate the same functionality with WPF. From my research so far, this cannot be done. I would love if someone can prove me wrong and demonstrate a pseudo code for both the WPF codebehind and the implentation in the Other Class.
Windows Form
public partial class ProgressForm : Form {
private bool abortFlag;
string _format;
/// <summary>
/// Set up progress bar form and immediately display it modelessly.
/// </summary>
/// <param name="caption">Form caption</param>
/// <param name="format">Progress message string</param>
/// <param name="max">Number of elements to process</param>
public ProgressForm( string caption, string format, int max )
{
_format = format;
InitializeComponent();
Text = caption;
label1.Text = (null == format) ? caption : string.Format( format, 0 );
progressBar1.Minimum = 0;
progressBar1.Maximum = max;
progressBar1.Value = 0;
Show();
Application.DoEvents();
}
public void Increment()
{
++progressBar1.Value;
if( null != _format )
{
label1.Text = string.Format( _format, progressBar1.Value );
}
Application.DoEvents();
}
public bool getAbortFlag()
{
return abortFlag;
}
private void button1_Click(object sender, EventArgs e)
{
button1.Text = "Aborting...";
abortFlag = true;
}
protected override bool ProcessDialogKey(Keys keyData)
{
if (Form.ModifierKeys == Keys.None && keyData == Keys.Escape)
{
button1.Text = "Aborting...";
abortFlag = true;
return true;
}
return base.ProcessDialogKey(keyData);
}
public void SetText(string text)
{
label1.Text = text;
System.Windows.Forms.Application.DoEvents();
}
public void SetProgressBarMinMax(int min, int max)
{
progressBar1.Minimum = min;
progressBar1.Maximum = max;
progressBar1.Value = 0;
}
public void IncrementProgressBar()
{
progressBar1.Value++;
System.Windows.Forms.Application.DoEvents();
}
public void HideProgressBar()
{
progressBar1.Visible = false;
System.Windows.Forms.Application.DoEvents();
}
public void ShowProgressBar()
{
progressBar1.Visible = true;
System.Windows.Forms.Application.DoEvents();
}
}
Some Method in Other Class
public class OtherClass(){
int counter = 0;
int n = numberOfIterations;
string s = "{0} of " + n.ToString() + String.Format(" elements processed.", counter.ToString());
string caption = "Duplicating ..";
using (ProgressForm pf = new ProgressForm(caption, s, n)){
//Something heavy to compute
foreach (var sheet in sheetsToDuplicate)
{
if (pf.getAbortFlag())
{
return;
}
counter ++;
pf.Increment();
}
}
}
I think this should be pretty straightfoward but I cannot find a single source out there to show how this can be achieved. For me, the important points to hightlight are:
The WPF (Window) ProgressBar control does not have reference to the Other Class. (because it will be shipped as a dll and should be able to adopt any scenario)
The loop is run in the Other Class.
Thank you.

Making a text editor with multiple pages using richtextboxes

i am trying to give my text editor multiple pages mode the problem is when the richtextbox reaches the last line it resizes and add a scroll bar which is not what i want, i made a code to transfer the last line of the richtextbox to the one that follows but it's moving the whole text instead and it's kind of sluggish, any help would be appreciated
public partial class Form1 : Form
{
protected static bool GetVisibleScrollbars(Control ctl)
{
int wndStyle = Win32.GetWindowLong(ctl.Handle, Win32.GWL_STYLE);
bool vsVisible = (wndStyle & Win32.WS_VSCROLL) != 0;
return vsVisible;
}
public Form1()
{
InitializeComponent();
}
List<RichTextBox> pages=new List<RichTextBox>();
int currentdocindex = 0;
public void AddPage()
{
RichTextBox B = new RichTextBox();
B.Size = richTextBox1.Size;
panel1.Controls.Add(B);
B.Location = new Point(pages[pages.Count - 1].Location.X, pages[pages.Count - 1].Location.Y + richTextBox1.Height + 20);
pages.Add(B);
B.SelectionIndent = 20;
B.SelectionRightIndent = 20;
B.Enter += new EventHandler(richTextBox_Enter);
}
private void richTextBox_Enter(object sender, EventArgs e)
{
int i = 0;
foreach (RichTextBox box in pages)
{
if (box == (RichTextBox)sender)
{
currentdocindex=i;
break;
}
i++;
}
label1.Text = (currentdocindex + 1).ToString();
}
private void Form1_Load(object sender, EventArgs e)
{
pages.Add(richTextBox1);
richTextBox1.SelectionIndent = 20;
richTextBox1.SelectionRightIndent = 20;
}
private void richTextBox1_Enter(object sender, EventArgs e)
{
int i = 0;
foreach (RichTextBox box in pages)
{
if(box==(RichTextBox)sender)
{
currentdocindex=i;
break;
}
i++;
}
}
bool added = false;
private void timer1_Tick(object sender, EventArgs e)
{
int correntPageIndex = currentdocindex;
if (GetVisibleScrollbars(pages[currentdocindex]))
{
if (!added)
{
AddPage();
added = true;
}
}
else
{
added = false;
}
}
if(GetVisibleScrollbars(pages[correntPageIndex]))
{
string LastLineText = pages[correntPageIndex].Lines[pages[correntPageIndex].Lines.Count() - 1];
int LastLineStartIndex = pages[correntPageIndex].Text.LastIndexOf(LastLineText);
pages[correntPageIndex].SelectionStart = LastLineStartIndex;
pages[correntPageIndex].SelectionLength = pages[correntPageIndex].Text.Length - 1;
LastLineText = pages[correntPageIndex].SelectedRtf;
pages[correntPageIndex].Text = pages[correntPageIndex].Text.Remove(LastLineStartIndex);
pages[correntPageIndex + 1].SelectionStart = 0;
pages[correntPageIndex+1].SelectedRtf = LastLineText;
}
}
}
public class Win32
{
// offset of window style value
public const int GWL_STYLE = -16;
// window style constants for scrollbars
public const int WS_VSCROLL = 0x00200000;
public const int WS_HSCROLL = 0x00100000;
[DllImport("user32.dll", SetLastError = true)]
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
}
RichTextBox is a pain for this sort of thing, because to mutate a small portion of text you have to actually select the text first (which it appears you're attempting to do) and ensure the change only affects that text. It's a little nasty on the memory usage, but you might be better served by determining how many characters you want per page and subscribing to the KeyDown Event to determine when you move to a new page. Try to adapt something like this and see if it works better.
public void MyKeyDownHandler(object sender, System.Windows.Forms.KeyEventArgs e)
{
if(this.CurrentPageControl.RTB.Text.Length >= MY_LIMITING_CONSTANT_I_SET)
{
MyPageUserControl mpuc = new MyPageUserControl();
mpuc.RTB.Text = this.CurrentPageControl.RTB.Text.Split(' ').Last();
thePageCollectionIPresumeYouHave.Add(mpuc);
this.CurrentPageControl = thePageCollectionIPresumeYouHave.Last();
mpuc.RTB.Focus();
}
}
Caveat: I did that entirely from memory and without a chance to read all of your code ( I had to skim) because I'm at work.
Another Caveat: I assumed you put your RichTextBoxes in a custom "page" control. If you didn't, I hope my code shows you why you might want to.

Access dynamic control from class in a thread

I'm working on a small project which deals with polling devices to check states of I/O controls. I had implemented a small project dealing with a particular device, but have decided that i would like to eventually implement different devices, and so have moved over to an class : interface approach. This has caused a few problems however, since i moved a lot of code around.
Before i moved the code around and such, i was accessing dynamic form controls by using a delegate as such;
if (result != null)
{
this.Invoke((MethodInvoker)delegate
{
txtOutput1.Text = (result[4] == 0x00 ? "HIGH" : "LOW"); // runs on UI thread
if (result[4] == 0x00)
{
this.Controls["btn" + buttonNumber].BackColor = Color.Green;
}
else
{
this.Controls["btn" + buttonNumber].BackColor = Color.Red;
}
});
}
This worked fine until i moved certain methods to a new class which inherits from an interface. I don't want to just set the dynamic buttons to public, and i'm not sure i can create get;set; for dynamic buttons, considering theres lots of them and are created on startup. Another problem is the this.invoke" command. I believe the invoke command doesn't work unless it's placed on a form...and now it's been moved to a class, so i need to look at another way of doing this.
Does anyone have any ideas as to where i should be heading with this?
EDIT 1:
The program is designed as a monitoring system for hardware devices that handle inputs/outputs. using these i can check if, for example, a door alarm has been triggered and such. The program itself in terms of forms / design is very simple. Currently i have a single form, which generates buttons based on information in a database, for example if there are 10 devices configured, there are 10 buttons. each of these shows green / red dependant on the hardware state.
My main form triggers a thread for each device which monitors it, but because i wished to have multiple types of device i moved them to different classes and an interface which handles all of the common methods. Currently i have a device class, which implements an interface. With regards to this question, i need to now access an instance of the single main form from which i am updating, rather than creating a new instance, so that i can use the new method i created when i moved said logic into the form itself.
EDIT 2:
IdeviceInterface bfdeviceimp = new bf2300deviceimp();
// some other declarations and initialize components
private void btnConnect_Click(object sender, EventArgs e)
{
updateUI();
}
public void updateUI()
{
DBConnector mDBConnector = new DBConnector();
int count = mDBConnector.Count() - 1;
DataTable dataTable = mDBConnector.Select("SELECT * FROM devices");
int x = 12;
int y = 65;
for (int i = 0; i <= count && i < 25; i++)
{
Button btnAdd = new Button();
btnAdd.Text = dataTable.Rows[i]["deviceDescription"].ToString();
btnAdd.Location = new Point(x, y);
btnAdd.Tag = i;
btnAdd.Name = "btn" + i.ToString();
btnAdd.BackColor = Color.Green;
var temp = i + 1;
this.Controls.Add(btnAdd);
this.Controls[btnAdd.Name].MouseClick += (sender, e) =>
{
int index = temp;
generalMethods.generatePopup(sender, e, index);
};
string address = dataTable.Rows[i]["deviceIP"].ToString();
int port = int.Parse(dataTable.Rows[i]["devicePort"].ToString());
ThreadStart workerThread = delegate { start(address, port, i); };
new Thread(workerThread).Start();
x = (x + 75);
if (i != 0 && (i % 5) == 0)
{
x = 12;
y = y + 30;
}
if (i == 25)
{
Button btnPreviousPage = new Button();
btnPreviousPage.Text = "<";
btnPreviousPage.Location = new Point(150, 350);
btnPreviousPage.Tag = "left";
this.Controls.Add(btnPreviousPage);
Button btnNextPage = new Button();
btnNextPage.Text = ">";
btnNextPage.Location = new Point(225, 350);
btnNextPage.Tag = "right";
this.Controls.Add(btnNextPage);
}
}
}
public void start(string address, int port, int i)
{
if (timer == null)
{
timer = new System.Timers.Timer(1000);
timer.Elapsed += delegate(object sender, ElapsedEventArgs e) { timerElapsed(sender, e, address, port, i); };
}
timer.Enabled = true;
// MessageBox.Show("Thread " + i + " Started.");
}
public void timerElapsed(object sender, ElapsedEventArgs e, string address, int port, int i)
{
bfdeviceimp.newconnect(address, port, i);
}
and then finally my device class:
class bf2300deviceimp : IdeviceInterface
{
public void newconnect(string address, int port, int buttonNumber)
{
//send data
byte[] bData = new byte[71];
bData[0] = 240;
bData[1] = 240;
bData[2] = 0;
bData[3] = 1;
bData[68] = 240;
bData[69] = 240;
bData[70] = this.newCalculateCheckSum(bData);
try
{
byte[] result = this.newSendCommandResult(address, port, bData, 72);
//form1.setAlarmColour(result, buttonNumber);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
Where would you suggest i put the statechanged handler?
You should to use an events-based approach for solving this problem, as is often the case when it comes to passing information between forms. Each of your devices should have a custom event that they define which is fired when the state of that device changes. The event should probably just be defined in the interface for interacting with that device. The form, when it creates the various device classes should subscribe to the event and in the event handler it should update the button/textbox appropriately.
This might be a fair bit to take in if you're not used to this style of programming. Feel free to ask for more details in the comments and I can elaborate on why I did something the way I did or what it actually does.
public Form1()
{
InitializeComponent();
//not sure if this is on initialization or in a button click event handler or wherever.
IDevice device = new SomeDevice();
device.StatusChanged += GetHandlerForDevice(1);
device.DoStuff();
IDevice device2 = new SomeDevice(); //could be another class that implements IDevice
device.StatusChanged += GetHandlerForDevice(2);
device.DoStuff();
}
/// <summary>
/// The handlers for device status changed only vary based on the button number for each one.
/// This method takes a button number and returns an event handler that uses that button number.
/// </summary>
/// <param name="buttonNumber"></param>
/// <returns></returns>
private EventHandler<StatusChangedEventArgs> GetHandlerForDevice(int buttonNumber)
{
//use currying so that the event handler which doesn't have an appropriate signature
//can be attached to the status changed event.
return (sender, args) => device_StatusChanged(sender, args, buttonNumber);
}
private void device_StatusChanged(object sender, StatusChangedEventArgs args, int buttonNumber)
{
this.Invoke((MethodInvoker)delegate
{
txtOutput1.Text = (args.CurrentStatus == IDevice.Status.Green ? "HIGH" : "LOW"); // runs on UI thread
if (args.CurrentStatus == IDevice.Status.Green)
{
this.Controls["btn" + buttonNumber].BackColor = Color.Green;
}
else
{
this.Controls["btn" + buttonNumber].BackColor = Color.Red;
}
});
}
public interface IDevice
{
event EventHandler<StatusChangedEventArgs> StatusChanged;
Status CurrentStatus { get; }
public enum Status
{
Green,
Red
}
void DoStuff();
// rest of interface ...
}
public class StatusChangedEventArgs : EventArgs
{
public IDevice.Status CurrentStatus { get; set; }
//can add additional info to pass from an IDevice to a form if needed.
}
public class SomeDevice : IDevice
{
public event EventHandler<StatusChangedEventArgs> StatusChanged;
private IDevice.Status _currentStatus;
/// <summary>
/// Gets the current status of the device this object represents.
/// When set (privately) it fires the StatusChanged event.
/// </summary>
public IDevice.Status CurrentStatus
{
get { return _currentStatus; }
private set
{
_currentStatus = value;
if (StatusChanged != null)
{
StatusChangedEventArgs args = new StatusChangedEventArgs();
args.CurrentStatus = value;
StatusChanged(this, args);
}
}
}
public void DoStuff()
{
//... do stuff
CurrentStatus = IDevice.Status.Green; //will fire status changed event
}
}
Move all the logic inside a method in the form and use it externally.
In your form create a property
public SynchronizationContext SyncContext { get; set;}
In form constructor add:
this.SyncContext = WindowsFormsSynchronizationContext.Current;
Make interface:
public Interface IChangeClient
{
void Process(<some_type> result); // place your logic here
}
(or somethng like that) and implement it in your Form to change your buttons and text.
Extend your original interface to use (maybe as a parameter) SynchronizationContext and IBackgroundChangeClient.
And than your code would look like this:
if (result != null)
{
oSyncContext.Post(new System.Threading.SendOrPostCallback(
delegate(object state)
{
IBackgroundChangeClient client = (state as object[])[0] as IBackgroundChangeClient
//i dont konw the type of this
var innerResult= (state as object[])[1];
client.Process(innerResult);
}), new object[] { oBackgroundChangeClient, result[4]});
}

Adding inputbox-like control to XNA game

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

make a WPF Popup not screen bound

The default behavior of a Popup is if it is placed where it would extend beyond the edge of the screen, the Popup will reposition itself. Is there a way to turn this behavior off?
I have a Popup that the user can drag around the screen. However, when it gets to the edges it gets stuck. It gets stuck on the edge and stays there until the mouse is dragged far from the edge. Also, I have two monitors and when the Popup is dragged to the edge the two monitors share I get flickering. The Popup flickers between the two monitors.
Just use interop to move your popups (drag them)
Here is code for Thumb which will track the drag process
region Thumb
private Thumb mThumb = null;
public Thumb Thumb
{
get { return mThumb; }
set
{
if (mThumb != value)
{
if (mThumb != null)
{
DetachThumb();
}
mThumb = value;
if (mThumb != null)
{
AttachThumb();
}
}
}
}
private void AttachThumb()
{
Thumb.DragStarted += Thumb_DragStarted;
Thumb.DragDelta += Thumb_DragDelta;
Thumb.DragCompleted += Thumb_DragCompleted;
}
private void DetachThumb()
{
Thumb.DragStarted -= Thumb_DragStarted;
Thumb.DragDelta -= Thumb_DragDelta;
Thumb.DragCompleted -= Thumb_DragCompleted;
}
private void Thumb_DragStarted(object sender, DragStartedEventArgs e)
{
mIsThumbDragging = true;
mPreviousDiffX = 0;
mPreviousDiffY = 0;
}
private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
if (mIsMoving)
{
return;
}
mIsMoving = true;
try
{
if (mIsThumbDragging)
{
var doubleDetaX = e.HorizontalChange + mPreviousDiffX;
var doubleDetaY = e.VerticalChange + mPreviousDiffY;
var deltaX = (int)doubleDetaX;
var deltaY = (int)doubleDetaY;
mPreviousDiffX = (double)deltaX - doubleDetaX;
mPreviousDiffY = (double)deltaY - doubleDetaY;
HostPopup.Move(deltaX, deltaY);
}
}
finally
{
mIsMoving = false;
}
}
private void Thumb_DragCompleted(object sender, DragCompletedEventArgs e)
{
mIsThumbDragging = false;
}
#endregion
The HostPopup class is subclass of Popup, and it hase the following methods using interop to move the window:
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
internal void Move(int deltaX, int deltaY)
{
if (mIsMoving)
{
return;
}
mIsMoving = true;
try
{
if (Child == null)
return;
var hwndSource = (PresentationSource.FromVisual(Child)) as HwndSource;
if (hwndSource == null)
return;
var hwnd = hwndSource.Handle;
RECT rect;
if (!GetWindowRect(hwnd, out rect))
return;
MoveWindow(hwnd, rect.Left + deltaX, rect.Top + deltaY, (int)Width, (int)Height, true);
}
finally
{
mIsMoving = false;
}
}
If you want the popup to behave more like a Window, I'd just make a Window instead of a Popup.
Having a popup that doesn't position itself like a standard popup, and allows you to drag it around the screen, just seems like a recipe for low usability and confusion.

Categories

Resources