How to create a control and manage its events in one method? - c#

I'm still stuck.
Assume that I've got a user control with a button. And an event called damnIt_ButtonClicked.
In the main window I want to emulate the control's lifetime like it is a modal dialog, although it's not.
I want to wrap everything into one method, it returns true if the Button on the control clicked.
public bool Show() {
var control = new ControlWithSingleButton();
bool result;
control.damnIt_ButtonClicked += (object sender, EventArgs args) =>
{
result = true;
};
MainWindowGrid.Children.Add(control);
MainWindowGrid.Visibility = Visibility.Visible;
return result;
}
Now. As you see the problem is this method will return always false;
But I need to return a result only when damnIt_ButtonClicked event fires. It means I have to put the thread on wait, till the user clicks button.
Right? Or how it should be done. Help me please....

You're going to need to re-architect your solution. Without knowing a broader scope of what you're trying to do, here's a possible solution.
private bool buttonResult;
public void Show() {
var control = new ControlWithSingleButton();
bool result;
control.damnIt_ButtonClicked += (object sender, EventArgs args) =>
{
this.ProcessButtonClick();
};
MainWindowGrid.Children.Add(control);
MainWindowGrid.Visibility = Visibility.Visible;
}
private void ProcessButtonClick()
{
this.buttonResult = true;
//do whatever you would have before if Show had returned true
}

You know what? I give up!
I decided to make the control a window, although it was strictly prohibited in given specifications to use any other windows but the Main. Anyway it's gonna be a chromeless, borderless transparent window, so nobody can see the difference.
Thank you so much.

Related

Need to click button twice to have an effect

The application is a machine control, so it needs access to ui to show status etc. (I know, goes against the recommendation to separate UI and work code, but it is what it is, at least for now). The issue boils down to this: When one button event handler is not finished, another button needs to be clicked twice. First click gives the focus to the button, next click fires the event.
Here is the issue simplified to extreme. There are two buttons and a label. Stop button needs two clicks to stop the machine:
bool Stop = true;
private void Start_button_Click(object sender, EventArgs e)
{
RunMachine();
}
private void Stop_button_Click(object sender, EventArgs e)
{
Stop = true;
}
private void RunMachine()
{
Stop = false;
Status_label.Text = "Running";
do
{
Application.DoEvents();
Thread.Sleep(50);
}
while (!Stop);
Status_label.Text = "Stopped";
}
How can I make the button to react to the first click?
DoEvents() is bad. Don't use it.
If you have to use it (e.g. as workaround), then you are adding technical debt and likely to pay in the future, similar to your case.
A better approach is to run work inside the task and use cancellation token, but in your case the minimum modification required is this (add async modifier to a method):
while (!Stop)
{
await Task.Delay(50);
// or
await Task.Run(() => Thread.Sleep(50));
}
The UI should be responsive now.
The latter is simulating synchronous code, put it instead of Sleep, don't forget to invoke if there you have to modify UI.
Thank you! I wasn't aware of the implications of Doevents, and using async and await is just as simple. I added a counter to show myself that the toy example is doing what I think it is. To make the answer complete and to help other noobs like me that might search answers for the same issue, here is the full example again. This works as wanted (stops with one click) and doesn't leave the RunMachine() running if the main form is closed without clicking stop. (My real application has enough code in the form closing event to prevent that, but I certainly wasn't aware of the trap.)
bool Stop = true;
private async void Start_button_Click(object sender, EventArgs e)
{
await RunMachine();
}
private void Stop_button_Click(object sender, EventArgs e)
{
Stop = true;
}
internal async Task RunMachine()
{
Status_label.Text = "started";
Stop = false;
int i=0;
do
{
await Task.Delay(500);
Status_label.Text = i.ToString();
i++;
} while (!Stop);
Status_label.Text = "Stopped";
}

Enable tooltips ActiveGantt 3.0.9.0

I'm putting together a form using the ActiveGantt 3.0.9.0 CSN control from http://www.bootes.co/EN/Default.aspx.
private void activeGanttCSNCtl1_OnMouseHoverToolTipDraw(object sender, ToolTipEventArgs e)
{
switch (e.EventTarget)
{
case E_EVENTTARGET.EVT_TASK:
TaskToolTipDraw(e);
e.CustomDraw = true;
return;
case E_EVENTTARGET.EVT_SELECTEDTASK:
TaskToolTipDraw(e);
e.CustomDraw = true;
return;
case E_EVENTTARGET.EVT_PERCENTAGE:
TaskToolTipDraw(e);
e.CustomDraw = true;
return;
case E_EVENTTARGET.EVT_SELECTEDPERCENTAGE:
TaskToolTipDraw(e);
e.CustomDraw = true;
return;
}
However e.EventTarget refuses to trigger on tasks. I can check the value and move around the control, and I can get EVT_Clientarea, EVT_Row, EVT_Column, EVT_None, EVT_Splitter, EVT_Timeline, EVT_TimelineScrollBar to trigger. However on tasks that I have added via this function it simply states that I am in EVT_Clientarea:
activeGanttCSNCtl1.Tasks.Add(AddingTask.Description, AddingTask.RowKey, AddingTask.StartTime, AddingTask.EndTime, AddingTask.ID, AddingTask.Style, AddingTask.Layer);
The tasks all show up fine, but simply do not trigger the Task event when I mouse over. If anyone could give me any guidance as to what I'm missing I would really appreciate it.
Thanks,
Mike
Thank you for asking this question. Try Overriding ToolTipOnMouseHover:
private void ActiveGanttCSNCtl1_ToolTipOnMouseHover(object sender, AGCSN.ToolTipEventArgs e)
{
switch (e.EventTarget)
{
case E_EVENTTARGET.EVT_TASK:
case E_EVENTTARGET.EVT_SELECTEDTASK:
ActiveGanttCSNCtl1.ToolTip.Visible = true;
return;
}
ActiveGanttCSNCtl1.ToolTip.Visible = false;
}
OnMouseHoverToolTipDraw fires when the Visible property of the ToolTip object is set to true, ToolTipOnMouseHover and OnMouseHoverToolTipDraw work in conjunction. In ToolTipOnMouseHover you generally perform calculations and do the actual drawing in OnMouseHoverToolTipDraw.
In the above example OnMouseHoverToolTipDraw would only be raised for tasks and ignored for all other objects. ActiveGantt handles Tasks and Tasks that are selected as two different objects.
Best Regards,
Julio Luzardo
Boötes Systems SAS

Better way to check if window got actually closed

I would like to know if there is a better way than the following to check if the window got closed, or if a Closing cancled the closing procedure?
Here we go with my way:
var window = Application.Current.Windows.FirstOrDefault(x => x is FooWindow);
if (window != null)
{
var gotClosed = false;
window.Closed += (sender, args) => gotClosed = true;
window.Close();
if (gotClosed == false)
{
//Close got cancled, by closing...
}
}
From checking the .NET source, I'm not too sure that IsDisposed is safe. There don't seem to be a lot of safe options though. The one I have been using so far without issues is checking the Visibility property for Visible after closing.
A cleaner approach might be creating your own class and overriding OnClosing() or OnClosed() though:
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
// Fires Closing event
base.OnClosing(e);
if (!e.Cancel)
{
// Window was allowed to close.
// Set IsClosed = true or something like that
}
}
There you can store the result in a property for example.
I'm not sure it's better than your solution, but after calling window.Close() the property IsDisposed gets true. So, you can check it:
if(window.IsDisposed)
{
....
}

C# WinForms cannot set focus

Im currently facing the problem that when i try to set focus on some control (textBox), nothing happens, maybe i just overlooked something.(somewhere i found that focus is "low-level" method and that select() should be used instead, however, it doesnt work as well)
From form Login, i launch new instance of EncryptPSW form
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
EncryptPSW ePSW = new EncryptPSW();
ePSW.setOsLog(false, this);
ePSW.ShowDialog();
}
On Button(which is located on EncryptPSW form ) click event i call fill method
public void fill()
{
if (textBoxPSW.Text.Length == 8)//psw has to be 8 chars long
{
if (save)//determinating whether save or fetch of data should be done
{ login.launchSave(textBoxPSW.Text,this); }
else { login.launchOpen(textBoxPSW.Text,this); }
}
else { MessageBox.Show("The password must contain 8 characters");}
}
Which launches either save or open method from Login (my problem is just with open, since during save i dont need to do anything with Focus)
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
setFocus();
}
After all the work is done, setFocus() should be called in order to set focus and other properties.
public void setFocus()
{
textBoxDatabase.Focus();
textBoxDatabase.SelectionStart = textBoxDatabase.TextLength - 1;
textBoxDatabase.SelectionLength = 0;
}
I tried so many different ways, like:
Calling setFocus() from within EncryptPSW_FormClosed
Calling whole open process after the EncryptPSW is closed (from within EncryptPSW_FormClosed)
and many more, however i dont remember it all.
In the case of Form_Closed the weird thing is, that when i tried to show a message box from there instead of setting focus (just to see where the problem might be), it's showed before the EncryptPSW form is closed.
My only guess about this is that the instance of EncryptPSW is somehow blocking Login form and it's controls
I hoped i described my problem well enough and that it makes at least a bit of sense ;]
Thanks in advance,
Regards,
Releis
Since the textbox is in the login form, and you are opening the EcryptPWS from it as a dialog (child), your login form will not be able to set focus to anything. You will need to set focus after it is closed. You can do this:
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
using(EncryptPSW ePSW = new EncryptPSW())
{
ePSW.setOsLog(false, this);
if (ePSW.ShowDialog() == DialogResult.OK)
{
textBoxDatabase.Focus();
}
}
}
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.DialogResult = DialogResult.OK;
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
}
OK this maybe the ugliest thing I saw round this but.
using
public void setFocus()
{
textBoxDatabase.Focus();
textBoxDatabase.SelectionStart = textBoxDatabase.TextLength - 1;
textBoxDatabase.SelectionLength = 0;
}
Change your code at
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
setFocus();
}
to
delegate void settingfocus();
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
settingfocus sf = new settingfocus(setFocus);
this.BeginInvoke(sf);
}
This worked for me
(Sorry for apparently thinking insert "this" before procedure, and change line x to this was legable)

C# "InvalidCastException" when trying to access webbrowser control from TimerCallback

Basically I have the same problem as this user:
How to check for TrackBar sliding with mouse hold and release
I fixed this using the first solution provided. However, when the timer is called, I want to call InvokeScript on a webbrowser control. InvokeScript runs without an error, but the javascript function is never called. When I call this script from like a button clicked event handler, the function is called properly.
I found out that when I try to access properties from the webbrowser control (like MessageBox.Show(webBrowser1.DocumentText), this throws a InvalidCastException.
// in constructor:
webBrowser1.AllowWebBrowserDrop = false;
webBrowser1.IsWebBrowserContextMenuEnabled = false;
webBrowser1.WebBrowserShortcutsEnabled = false;
webBrowser1.ObjectForScripting = this;
timer = new System.Threading.Timer(this.TimerElapsed);
private void trackBar2_ValueChanged(object sender, EventArgs e)
{
timer.Change(500, -1);
}
private void TimerElapsed(object state)
{
this.webBrowser1.InvokeScript("jmp_end");
MessageBox.Show(this.webBrowser1.DocumentText);
timerRunning = false;
}
private void TimerElapsed(object state)
{
WebBrowser brw = getBrowser();
brw.Document.InvokeScript("jmpend");
MessageBox.Show(brw.DocumentText);
timerRunning = false;
}
Does anyone know what I am doing wrong here? Or is there another way to get the same result?
After comments about InvokeRequired, this sounds exactly like what I need.. But I can't get it working.. This is what I made from the sample code from C# System.InvalidCastException
public delegate WebBrowser getBrowserHandler();
public WebBrowser getBrowser()
{
if (InvokeRequired)
{
return Invoke(new getBrowserHandler(getBrowser)) as WebBrowser;
}
else
{
return webBrowser1;
}
}
private void TimerElapsed(object state)
{
WebBrowser brw = getBrowser();
brw.Document.InvokeScript("jmpend");
MessageBox.Show(brw.DocumentText);
timerRunning = false;
}
What have I missed here?
The caller (the timer) is on a different thread than the control was created on.
See Control.InvokeRequired Property
Sample code that should address your issue is posted on this question: C# System.InvalidCastException
I had the same problem. As pointed out by Kevin P. Rice, the caller is on a diferente thread than the one the control was created on. A simple solution for this is to use this.Invoke() everytime the thread needs to interact with a control, therefore, if you desire to have the browser invoke a script, and you wish to call it from inside a separate thread, just do it like this:
this.Invoke(new Action(() => { brw.Document.InvokeScript("jmpend"); }));
Or if you wish to change the property of the browse or another control within the form:
this.Invoke(new Action(() => { button1.Enabled = false; }));
If the declaration of your thread is in another scope than that of your form, and you can't use the this keyword, you need to find a way to reference the current instance of the form.
I hope this helps. :)

Categories