Focus handling for async behaviour - do not steal already set focus - c#

[introduction] I have sample WPF application. If I click a button, new window is opened. I need to wait some time for data to be loaded inside it. Rather than passively wait, I want to do some other stuff in the meantime. I can for example open some context menu. This situation is illustrated on the screen below:
This wait-for-me window, just after the loading is completed (data is ready to show), fires an event where focus is set to the grid:
void DataLoaded(object sender, EventArgs e)
{
grid.Focus();
grid.SelectedIndex = 0;
}
[current issue] Unfortunately, in the very same moment, our recently opened context menu has just disappeared. The focus has been forcefully stolen from it. Annoying final effect is shown below:
[desired effect] What would be the happy end? It would be no automatic focus, if user just changed it to any other element (like context menu). In the other words - do not steal the focus.
The code modification could be:
void DataLoaded(object sender, EventArgs e)
{
if (Magic.FocusNotChanged)
{
grid.Focus();
grid.SelectedIndex = 0;
}
}
But, what is the Magic? Some global publish subscribe mechanism which allows or denies automatic focus changes? Some handler which is spying focus changes?
BTW: This particular application shown above is just artificially extracted from much wider context. Do not pay much attention to the layout implemented here. Some generic mechanism has to be invented, not related to this specific button or context menu. Any clues?
Regards.

The solution is prosaic - to take focus in a non-invasive way, check if window is active that time:
void DataLoaded(object sender, EventArgs e)
{
if (this.IsActive)
{
grid.Focus();
grid.SelectedIndex = 0;
}
}
Some further enhancements:
Suppose we'd like to have more generic solution. What if we want to set the focus from within particular control hosted by some window, not from the window itself (lack of IsActive property)? We need to find its parent window, to check if it is still active. What's more, let's suppose this control contains a bunch of child controls, and we would like to set focus to some particular child. Look at this:
void DataLoaded(object sender, EventArgs e)
{
var window = this.GetParent<Window>();
if (window.IsActive)
{
var grid = this.GetChild<DataGrid>();
grid.Focus();
}
}
You can see the usage of two helper methods. Their implementation is given below:
public static T GetParent<T>(this DependencyObject child) where T : DependencyObject
{
if (child == null) return null;
// get parent item
var parentObject = VisualTreeHelper.GetParent(child);
// we’ve reached the end of the tree
if (parentObject == null) return null;
// check if the parent matches the type we’re looking for
var parent = parentObject as T;
// return parent if match or use recursion to proceed with next level
return parent ?? GetParent<T>(parentObject);
}
public static T GetChild<T>(this DependencyObject parent) where T : DependencyObject
{
if (parent == null) return null;
T result = null;
var childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < childrenCount; i++)
{
var childObject = VisualTreeHelper.GetChild(parent, i);
var child = childObject as T;
if (child == null)
result = childObject.GetChild<T>();
else
{
result = (T) childObject;
break;
}
}
return result;
}

Related

Buttons work only on the second click on custom popup c#

when i move between forms buttons works as expected, but when im closing my custom pop up only seconds click on any button will trigger it
this is the code im using to load my custom popup
public void loadPopUp(Form from, Form to)
{
to.Tag = from;
to.Show(from);
}
and this is the code my using to close my custom popup
public void closePopUp(string formName)
{
for (int i = Application.OpenForms.Count - 1; i >= 0; i--)
{
string name = Application.OpenForms[i].Name; //for debugging only
if (Application.OpenForms[i].Name == formName)
Application.OpenForms[i].Close();
}
}
note: this is not asp.net application
You should have something else in your code or you make something with the main window that changes the normal behavior of the forms engine. I have tried to build a sample app with LinqPAD using the code above and I have a normal behavior as expected. When I close the popup the focus is restored to the main form.
Nevertheless you could use the main form saved instance in the Tag property to call the Activate method and restore the focus to the main form.
public void closePopUp(string formName)
{
// No need to loop over all application OpenForms if you just want
// to close this popup
// for (int i = Application.OpenForms.Count - 1; i >= 0; i--)
// {
// string name = Application.OpenForms[i].Name; //for debugging only
// if (Application.OpenForms[i].Name == formName)
// Application.OpenForms[i].Close();
//}
var mainForm = this.Tag as Form;
this.Close();
if(mainForm != null)
mainForm.Activate();
}
Note, I use a cast to the base Form class, so your code doesn't depend on the popup created by a particular class instance. This is possible because Activate is a base class method.

Add a default user control everytime any other user control is closed in a panel

I have been trying to load a default user control every time any other user control on the same panel is closed by the user. I have a panel named MainContainer and when the main form loads I am calling the following method to load that default user control named welcome.
public void AddUserControlWelcome()
{
MainContainer.Controls.Clear();
welcome.Dock = DockStyle.Fill;
MainContainer.Controls.Add(welcome);
}
I have a menustrip button which calls the following method,
private void sellItemsToolStripMenuItem_Click(object sender, EventArgs e)
{
AddUserControlSellManager();
}
And it is defined as,
public void AddUserControlSellManager()
{
MainContainer.Controls.Clear();
sellManager.Dock = DockStyle.Fill;
MainContainer.Controls.Add(sellManager);
}
So, there is a button on sellManager user control which actually closes sellManager. And after that I am invoking AddUserControlWelcome() again from MainContainer_ControlRemoved(object sender, ControlEventArgs e) but the application is crashing and I don't know why.
I think, it is clear why you having this issue. MainContainer_ControlRemoved called not only when you remove your "sell" but "welcome" too. So, the culprit I believe is the fact that you do add control on such event as MainContainer_ControlRemoved, which you shouldn't do. As good as .Net is, sometimes you have to stay away from using certain events for certain purposes , or you run into issues.
Try to do something like this. Considering that your surface can host only one control at the time
class SurfaceManager
{
private Control _defaultCtrl;
private bool _currentDefault;
private Control _surface;
void SurfaceManager(Control _surface, Control defaultCtrl)
{
_surface = surface;
_defaultCtrl = defaultCtrl;
_surface = surface.Controls.Add(_defaultCtrl);
_currentDefault = true;
}
public Control Add(Control ctrl)
{
Control c = null; // Returning removed control so you can do something else with it
if (_surface.Controls.Count > 0)
{
if (!_currentDefault)
c = _surface.Controls[0];
_surface.Controls.Clear();
}
_surface = surface.Controls.Add(ctrl);
_currentDefault = false;
Return c;
}
public Control Remove()
{
if (_currentDefault) Return // Current is default - do nothing
Control c = null; // Returning removed control so you can do something else with it
if (_surface.Controls.Count > 0)
{
c = _surface.Controls[0];
_surface.Controls.Clear();
}
_surface = surface.Controls.Add(_defaultCtrl);
_currentDefault = true;
Return c;
}
}
Now, in your class create instance of this manager and use Add or Remove. Remove will automatically bring on the Welcome screen

Threads with UI (Canvas)

I'm trying to generate a Thread for the redrawing-function of my existing poly-drawing. I read here it is possible that UI can be realized in Threads see here LINK but I cant use it on my redrawSingelPoly() function.... Any ideas how I can use redrawSingelPoly() as an thread ?
In my MainWindow.xaml.cs:
Is called when the user press a button on my main window:
private void SingleSelectedMeasurement(object sender, RoutedEventArgs e)
{
System.Windows.Controls.Button button = (System.Windows.Controls.Button)sender;
RedrawSingelMeasurement(Convert.ToInt16(button.Tag));
}
private void RedrawSingelMeasurement(int selectedMeasurement)
{
selectedMeasurement = selectedMeasurement - 1;
for (int i = 0; i < measurements.Length; i++)
{
if (selectedMeasurement != i)
{
measurements[i].draw = false; //block drawing
}
else
{
measurements[i].draw = true; // remove block for drawing
}
}
measurements[selectedMeasurement].redrawSingelPoly();
}
In my Measurement.cs:
public void redrawSingelPoly()
{
Polyline poly = new Polyline();
poly.Stroke = colorBrush;
poly.StrokeThickness = basicLineThick;
//first clean all
coordinateSystem.Children.Clear();
poly.Points = points;
//draw
coordinateSystem.Children.Add(poly);
}
You cannot access to DependencyProperties of DependencyObject (in your case: coordinateSystem) from the thread different to the one it's related to.
If you want to speed up your application, you should create custom control, override its OnRender method and draw your custom graphics there: it will remove a lot of logical and visual tree logic and will work a lot faster.
Ideally one window only can run on one Dispatcher, however you can put different visuals in different threads by HostVisual but in very limited scenarios. Maybe this article can help you:
http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx
What the article you mention talks about is actually just having one thread for each window. Drawing dedicated elements in a different thread is not possible.
private void SingleSelectedMeasurement(object sender, RoutedEventArgs e)
{
var button = (System.Windows.Controls.Button)sender;
Task.Factory.StartNew (
() => OnUi(RedrawSingelMeasurement(Convert.ToInt16(button.Tag))));
}
//here's a sample on how to get despatcher for the ui thread
private void OnUi (Action action)
{
if (_dispatchService == null)
_dispatchService = ServiceLocator.Current.GetInstance<IDispatchService>();
//or _dispatchService = Application.Current.Dispatcher - whatever is suitable
if (_dispatchService.CheckAccess())
action.Invoke ();
else
_dispatchService.Invoke(action);
}

Better way to programmatically Lock/Disable multiple UI controls on Ribbon bar

I have been wondering, what would be the better way to implement Locking/Disabling a bunch of Ribbon UI controls by just a click?
Currently, my approach is kinda simple and basic:
private void tbtnLock_Click(object sender, RibbonControlEventArgs e)
{
if (tbtnLock.Checked)
{
control1.Enabled = false;
control2.Enabled = false;
control3.Enabled = false;
//...
//controlN.Enabled = false;
}
else
{
control1.Enabled = true;
control2.Enabled = true;
control3.Enabled = true;
//...
//controlN.Enabled = true;
}
}
I think it's okay if we only have just a few controls but once we add more and more controls onto the Ribbon bar, I don't think it'd be a good coding practice to do things like above.
Is there any cleaner and neater approach to this? Am I able to get the collection of all the controls on the Ribbon bar? Hopefully someone can give me some pointers here? Thanks very much.
EDIT:
Revised code below:
private void tbtnLock_Click(object sender, RibbonControlEventArgs e)
{
toggleUILockState();
}
private void toggleUILockState()
{
if (group1.Items != null)
{
foreach (RibbonControl c in group1.Items)
{
if (c.Name != "tbtnLock")
{
c.Enabled = !tbtnLock.Checked;
}
}
}
}
I think it looks a lot better than the previous version. Thanks everyone for the help.
Well certainly the first step to improve the code would be to remove the if statement and assigned the enabled state of the control directly with the checked state of the tbtnLock control like...
control1.Enabled = !tbtnLock.Checked;
control2.Enabled = !tbtnLock.Checked;
that would cut your code if half straight away. You may want to assign that to a bool first incase you want to do additional processing on it later (maybe some other object helps determining lock state for example)
bool isEnabled = !tbtnLock.Checked;
control1.Enabled = isEnabled;
control2.Enabled = isEnabled;
Further than that I would need to know what "ribbon" control you are using. Do you have a link?
But as you have hinted, I would want to look at trying to find a colleciton of controls, loop through them, check if the control is not the tbtnLock control and disable/enable as needed.
Also, I would recommend move all this code to a function outside of the event handle, in case you need to call this method from other code. Something like...
private void tbtnLock_Click(object sender, RibbonControlEventArgs e)
{
UpdateRibbonState();
}
private void UpdateRibbonState(){
//Code goes here
}
EDIT: Making assumtion that a "group" (as described in comments) has a collection of controls...
foreach(Control c in group.Controls)
{
if(c.Name != "tbtnLock")
{
c.Enabled = !tbtnLock.Checked;
}
}
I am not familiar with any built-in .Net ribbon controls and as there is no link to a 3rd party set) I am making a best guess at the properties available for a "group"
Seems like you could put all your UI controls into one list and then iterate it to enable/disable them all at once. Something like (untested/pseudocode):
List<RibbonBase> listMyControls = new List<RibbonBase>()
{
control1, control2, control3, ... , controlN
};
foreach (var control in listMyControls)
{
control.Enabled = !tbtnLock.Checked;
}
Here is some code to extend this to an entire tab.
/// <summary>
/// Enable or Disable all buttons in all groups of the RibbonTab to match toggleButtonActive
/// toggleButtonActive remains enabled
/// </summary>
/// <param name="enabled"></param>
private void SetUILockState(bool enabled)
{
foreach (RibbonGroup group in myRibbonTab.Groups)
{
if (group.Items != null)
{
foreach (RibbonControl c in group.Items)
{
if (c.Name != "toggleButtonActive")
{
c.Enabled = enabled;
}
}
}
}
// TODO handle right click menus as well
}

ASP.NET dynamic data: Table is not shown anymore after inserting data if FormView.Controls is accessed on Page.Initialized event

Lately, I received a bug report for Ninject.Web that it is not working properly together with ASP.NET dynamic data. The problem is that on postback (e.g. when Inserting, Deleting, Editing a record) the table is not shown anymore.
Some debuging showed that the problem is caused by a IHttpModule that recursively iterates through all controls of a page after it is initialized. As soon as this module accesses the Controls property get accessor of FormView or GridView the problem occurs. If this type of controls is skiped everything is fine. The following code shows the module:
public class NinjectHttpModule : DisposableObject, IHttpModule
{
private HttpApplication httpApplication;
public void Init(HttpApplication context)
{
this.httpApplication = context;
this.httpApplication.PreRequestHandlerExecute += this.OnPreRequestHandlerExecute;
}
private static void InjectUserControls(Control parent)
{
if (parent == null)
{
return;
}
foreach (Control control in parent.Controls)
{
if (control is UserControl)
{
// KernelContainer.Inject(control); This is irrelevant for the question.
}
InjectUserControls(control);
}
}
private void OnPreRequestHandlerExecute(object sender, EventArgs e)
{
var page = this.httpApplication.Context.CurrentHandler as Page;
if (page == null)
{
return;
}
KernelContainer.Inject(page);
page.InitComplete += (src, args) => InjectUserControls(page);
}
}
If this code is changed so that the iteration through the child controls of DataBoundControls is delayed to the DataBound event everything is fine. Shown by the next code snippet:
private static void InjectUserControls(Control parent, bool skipDataBoundControls)
{
if (parent == null)
{
return;
}
if (skipDataBoundControls)
{
var dataBoundControl = parent as DataBoundControl;
if (dataBoundControl != null)
{
dataBoundControl.DataBound += InjectDataBoundControl;
return;
}
}
foreach (Control control in parent.Controls)
{
if (control is UserControl)
{
KernelContainer.Inject(control);
}
InjectUserControls(control, skipDataBoundControls);
}
}
private static void InjectDataBoundControl(object sender, EventArgs e)
{
var dataBoundControl = sender as DataBoundControl;
if (dataBoundControl != null)
{
dataBoundControl.DataBound -= InjectDataBoundControl;
InjectUserControls(dataBoundControl, false);
}
}
Because I'm completely unfamiliar with System.Web.DynamicData I'd like to know some things to get a better feeling about how to fix this bug:
Why does this problem occur? I mean it's only a simple read access to the Controls property.
What side effects can the change above have?
Is it still early enough to inject the controls after the data bound event?
Do you think this is a valid bug fix for this problem?
Certainly puzzling behavior, as can sometimes happen in WebForms with the many phases of execution.
Even though it's just a simple read access to the Controls property, this property can actually do a lot of work to return the child controls. In particular, it can't return the child controls unless they have been created, and that creation normally does not occur until later in the page life cycle. So by accessing it in InitComplete, the children end up getting created prematurely, before some important Dynamic Data hookups have happened, causing some controls to be missing. Yes, I realize that the end result behavior seems to make little sense, which is why some people favor the straightforwardness of MVC :)
As an alternate possible workaround, could you try moving your injection from InitComplete to PreLoad? e.g.
page.PreLoad += (src, args) => InjectUserControls(page);
I'm pretty sure that'll address the problem, though I'm less sure whether this will cause issues with your KernelContainer.Inject logic. Give it a try, since it's simpler than your workaround.
If that doesn't work, I think your workaround is ok, as it delays the enumeration until the children are created. As for 'Is it still early enough to inject the controls after the data bound event', I think that depends in exactly what KernelContainer.Inject does, and what expectations it has on the state of the control.

Categories