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
Related
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";
}
In my app, I want to be know when play state changes. But I don't know how to subscribe to the event and get the current state. How can I do that? thanks.
I see an statement in MSDN, but couldn't understand what it means and how to implement it:
In Windows Phone 8, you can check the PlayStateChangedEventArgs to
determine both the CurrentPlayState and the IntermediatePlayState that
occurred before the audio player entered the current play state.
Details:
in the main page I do this:
public MainPage()
{
BackgroundAudioPlayer.Instance.PlayStateChanged += new EventHandler(Instance_PlayStateChanged);
}
then
private void Instance_PlayStateChanged(object sender, EventArgs e)
{
var playerState = BackgroundAudioPlayer.Instance.PlayerState;
}
But I feel this is not the correct way to use event and eventargs. it also doesn't give me the correct latest value.
The PlayerStateChanged event is definitely the right way to determine changes, but it won't fire when you subscribe to it so you won't get the current state. Try something like this instead:
BackgroundAudioPlayer audioPlayer = BackgroundAudioPlayer.Instance;
public MainPage()
{
audioPlayer += OnPlayStateChanged;
OnPlayStateChanged(audioPlayer.PlayerState);
}
private OnPlayStateChanged(object sender, EventArgs e)
{
OnPlayStateChanged(audioPlayer.PlayerState);
}
private OnPlayStateChanged(PlayState state)
{
// Process state here
}
Having said that, there are two major things worth pointing out.
Firstly, BackgroundAudioPlayer is an extremely volatile API. It will commonly throw exceptions if not in the correct internal state. Feel free to use the extension methods I developed for Podcaster: https://gist.github.com/richardszalay/8552812
Secondly, PlayerStateChanged is not fired when the playback position changes. For that, I'd recommend using a DispatcherTimer and updating your display via my TryGetPosition method (but only when GetTrackOrDefault() returns non-null). I'd also recommend using a sub-second timer (200-300ms) to keep the "ticking" correct. When the PlayerState changes to FastForwarding or Rewinding, update the timer to 20-30ms, and restore it when it returns to Playing.
Use this solution as well as link you would get solution:-
enter link description here
void Instance_PlayStateChanged(object sender, EventArgs e)
{
switch (BackgroundAudioPlayer.Instance.PlayerState)
{
case PlayState.Playing:
playButton.Content = "pause";
break;
case PlayState.Paused:
case PlayState.Stopped:
playButton.Content = "play";
break;
}
if (null != BackgroundAudioPlayer.Instance.Track)
{
txtCurrentTrack.Text = BackgroundAudioPlayer.Instance.Track.Title +
" by " +
BackgroundAudioPlayer.Instance.Track.Artist;
}
}
I have a button click event handler with a switch case in it that controls multiple buttons in one event handler.
I need to use a queue because while one button is clicked and doing some processing, second button click won't interfere with the first button click, but added to the queue. I don't want to use .enabled=false; because it'll discard the second click completely, and I'm currently editing someone's software at work so I don't want to break things that I don't know, so what are you suggesting?
The best idea, I think, is to create a producer/consumer queue.
Another question is explaining this technique.
Basically, the idea is to have a worker thread that will consume a queue to get the job to do, while other thread produce job by queuing operation in the queue.
I did succeed this with System.Collections.Queue
The code is :
private Queue<Button> Button_Queue = new Queue<Button>();
private bool isProcessing = false;
private void Button_Click((object sender, EventArgs e){
if(isProcessing){
Button_Queue.Enqueue(this);
}
else
{
isProcessing = true;
// code here
isProcessing = false;
while(Button_Queue.Count > 0){
Button_Queue.Dequeue().PerformClick();
}
}
of course mine is slightly different from this because I need to pass some variables and my click method is modified for this.
Dirty, but simple solution.
public partial class DataRefresh : Form //DataRefresh is just "some form"
{
...
...
public DateTime ClickTime; //Time when click is processed by system
public DateTime LastExecutionRunTime = DateTime.MinValue; //Time when the all the click code finish
private void buttonDataRefresh_Click(object sender, EventArgs e)
{
ClickTime = DateTime.Now;
if (ClickTime.Subtract(LastExecutionRunTime).TotalSeconds < 5 )
{
//It will keep returning - hopefully until all events in que are satisfied
return;
}
//Long running code
//Importing whole table from remote DB
...
...
//End of the Long running code
LastExecutionRunTime = DateTime.Now;
}
}
I have a treeview with checkboxes and I have the following handler for the "AfterCheck" event:
private void trvAvailableFiles_AfterCheck(object sender, TreeViewEventArgs e)
{
if (!_isCheckingInProgress)
{
trvAvailableFiles.BeginUpdate();
var nodePath = e.Node.Tag.ToString();
bool isChecked = e.Node.Checked;
e.Node.Nodes.Clear();
try
{
_fileTreeLogic.GetChildNodes(e.Node, true);
e.Node.ExpandAll();
_isCheckingInProgress = true;
SetChildrenCheckState(e.Node, isChecked);
_isCheckingInProgress = false;
}
finally
{
trvAvailableFiles.EndUpdate();
}
}
}
If you look closely you'll see that I'm checking if "_isCheckingInProgress". If it is not, then I proceed and expand all the nodes and call the SetChildrenCheckState() method. The problem I have encountered is that SetChildrenCheckState() will subsequently cause each child node to all fire the AfterCheck event for its own node.
My question is, is there a more clean way to allow the first AfterCheck event to fire but not the subsequent ones? It seems kind of hackish that I have to have an instance bool variable to check and set.
Use: if(e.Action != TreeViewAction.Unknown) instead of if (!_isCheckingInProgress). See TreeViewAction .
When the user uses the keyboard or mouse to check the checkboxes, e.Action will be TreeViewAction.ByKeyboard or TreeViewAction.ByMouse.
The MSDN provides this as example code for the TreeView.AfterCheck Event.
Edit 1: Obviously, if you're setting the checkbox yourself in code, move the code in the event handler to a new function and have the code that sets the checkbox call it directly. The point of this solution is to let you use event handlers for user input without having those events get triggered when you set the checkboxes yourself via code.
Edit 2: See Spencer's answer for an explanation of my comment in Edit 1
One recommendation you'll see occasionally around SO is to not put a lot of code into event handlers themselves. There are a number of reasons for this. First, in your case it would be easier to understand a call like:
private void trvAvailableFiles_AfterCheck(object sender, TreeViewEventArgs e)
{
if (!_isCheckingInProgress)
{
_isCheckingInProgress = true;
try { GetAvailableFiles(); } catch {}
_isCheckingInProgress = false;
}
}
And to place the rest of your code in GetAvailableFiles(). This creates a separation between event code and action code which most people would agree is a worthwhile distinction to make.
Second, which may or may not be applicable in your case is that multiple events can cause the same action. Such as mnuFileQuit_Click and btnClose_Click as an obvious example. If both make calls to CloseApplication() it removes a lot of redundant code.
Personally, I use a function that removes and then adds the event.
private void trvAvailableFiles_AfterCheck(object sender, TreeViewEventArgs e)
{
EnableEvents(false);
trvAvailableFiles.BeginUpdate();
var nodePath = e.Node.Tag.ToString();
bool isChecked = e.Node.Checked;
e.Node.Nodes.Clear();
try
{
_fileTreeLogic.GetChildNodes(e.Node, true);
e.Node.ExpandAll();
SetChildrenCheckState(e.Node, isChecked);
}
finally
{
trvAvailableFiles.EndUpdate();
}
EnableEvents(true);
}
private void EnableEvents(bool bEnable)
{
if(bEnable)
cbWhatever.OnChecked += EventHandler;
else
cbWhatever.OnChecked -= EventHandler;
}
No, there's no cleaner way to do what you've shown. I'm not really sure why you feel that variables are a "hack" approach. Setting a flag is a common technique used when writing UI code.
The real hack would be some obscure way to prevent raising the event the first time, but not subsequent times. Future maintenance programmers are guaranteed to understand how setting a flag works; they're not guaranteed to appreciate the "elegance" of your alternative approach.
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.