Detect unpost of ttk::combobox - combobox

I have a Tcl/Tk program. In it I have a combobox, like so:
set cb [ttk::combobox .cb -state readonly -textvariable selection -postcommand [list choices .cb]]
The proc choices runs when the combobox posts, which is what I want. My question is, how do I detect when the combobox unposts?
I've tried both binding on <<ComboboxSelected>> and setting a variable trace on selection. The problem with each is that they only fire when the user actually changes the selection. I need some way to always detect when the combobox unposts.
Thanks!
edit
What I'm trying to accomplish: When the combobox posts it presents the user with a list of options. I don't expect the user to know what the options mean, therefor I am highlighting the options visually in a different area of my program. I have this highlighting triggering and working well with -postcommand. The issue is to know when to turn the highlighting back off.
<<ComboboxSelected>> doesn't fire if the user doesn't change the selected value.
<Leave> and <FocusOut> fire too soon (e.g. as soon as the box posts).

The combobox's popdown is actually its own nest of windows, and if your combobox is called .cb then the popdown has the imaginative name .cb.popdown (note that this implementation and is not guaranteed). If you add a binding to that widget's <Unmap> event you'll get to see the unposting; <Unmap> events are exactly the notifications sent when a window ceases to be displayed in the virtual desktop layer sense (as opposed to just ceasing to be visible, say because there's another window on top; there's events for that too, but they're not cross-platform).
The tricky bits:
The popdown is usually created when needed, i.e., the first time it appears. You need the window to exist (but not necessarily be visible) before you bind to it. You can get the handle of the popdown widget with ttk::combobox::PopdownWindow, which will make the widget if it doesn't already exist. (It's part of the implementation, but it is more likely to be stable than the name.)
set popdown [ttk::combobox::PopdownWindow .cb]
bind $popdown <Unmap> {yourCallback %W}
It is possible to dig around within the internal arrangement of the popdown, but I don't recommend it; it's much more likely to change without warning.
Binding to the toplevel has the usual issues with events also being delivered for subwindows. Your callback should check that the event it has been given is actually for the toplevel:
proc yourCallback {w} {
if {$w ne [winfo toplevel $w]} { return }
# The rest of your code here...
}

The window name of the listbox used by ttk::combobox is:
set popdown [ttk::combobox::PopdownWindow .combobox].f.l
I believe this is what you need.
bind <Leave> $popdown mycommand

Related

Why does a control with Visible = false set early on in its life, ignore changes in bound properties?

I recently came upon what seems to be an oddity of win forms property binding in that if a control starts out life* as invisible, the control doesn't seem to see changes in the bound property. In this case I was binding a label's .Visible property to Properties.Settings.Default.BooleanSetting (default False) in the application settings. I was also binding a checkbox .Checked to the same setting, the idea being that toggling the checkbox to checked would cause the label to appear and unchecked would cause it to disappear. All setup was done in the forms designer
This only worked reliably when the setting was True at application startup. If the setting was False, the checkbox would be unchecked, and the label invisible but checking the checkbox would not show the label. Upon debugging it turned out that the value of the setting was true, so the checkbox had updated it, but the label's Visible property was still false
Other bound properties didn't seem to have a problem if the label was initially visible; Enabled could be bound and would reliably flip state when the checkbox was toggled regardless of the initial value of the setting. The problem seemed to be specifically if the label started out invisible - an invisible label with its Enabled property bound to that boolean wouldn't change Enabled state.
The workaround seemed to be to store Properties.Settings.Default.BooleanSetting, set it to true before calling InitializeComponent() then revert it. In this case the form would show, and checking the box would reveal the invisible label
var b = Properties.Settings.Default.FormatWithoutConfirmation;
Properties.Settings.Default.FormatWithoutConfirmation = true;
InitializeComponent();
Properties.Settings.Default.FormatWithoutConfirmation = b;
I first drew the conclusion (with some help from a related question here - asking about how to make it work) that a control needs to be initially visible for binding to work, and having a control invisible puts the control in the catch 22 of never being Visible in order to see the change in Visible..
It does seem to be a one time setup thing rather than a "if a control ever goes invisible it stops listening" thing because if it always affected an invisible control then a control that started out visible would go invisible and never return - this isn't the case, and initially visible controls toggle visibility on and off perfectly.
One sticking point for me in the initially-visible theory is that, according to the code in the windows designer, binding happens before the visibility is set:
this.label4.AutoSize = true;
this.label4.DataBindings.Add(new System.Windows.Forms.Binding("Enabled", global::Namespace.Properties.Settings.Default, "BooleanSetting", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
this.label4.Enabled = global::Namespace.Properties.Settings.Default.BooleanSetting;
this.label4.Location = new System.Drawing.Point(100, 100);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(35, 13);
this.label4.TabIndex = 11;
this.label4.Text = "label4";
this.label4.Visible = false;
At the time binding is set up here, Visibility must be True because that's the default for the Visible property. It seems like there is something else that happens later to finish off the setup, or that there is a critical moment early in a control's life (but not this early) where something happens to it that determines whether it will or won't listen to bound property changes
I get the feeling that the answer might be out there somewhere in MSDN as one of the answers in that question I linked to said...
assume that binding to a control's Visible property is broken, despite the fact that it sometimes works. See http://support.microsoft.com/kb/327305, which says as much
...but the link given to MSDN has rotted. Another related question I read mentioned that initially invisible controls don't have handles when created and mooted it as a possible factor
*not sure when a control's life starts, ref the "visibility is set after binding is created" note
As was suggested, as long as a Windows Forms Control doesn't have a valid Windows Handle, DataBinding will not be active.
I first realized this many years ago when I had to work on a Window with a Tab Control on it. It took me a while to understand why Control on Tabs that were initially invisible would not properly bind, but on the first Tab, all was fine.
Once I understood what the problem was, I wanted to find a nice way to work around it, but at that time, the project leader didn't have the budget. So, I went down the path of laziness, and simply cycled through all Tabs, giving them a chance to be visible very briefly, and all Controls on those Tabs then had the possibility of getting a valid Handle.
It sucked real bad because the Tab pages could be seen flickering during the process. This ended up never being fixed, and the application was released as-is. Needless to say that the other programmers and myself were not too happy about it, but the customer never complained. Go figure.
So, the Control must be visible to get its Handle, and then can be hidden.
Good luck forward.

How to get an event when WinForms TreeView items gets cleared

I am using a standard TreeView in a WinForms application and everything works fine except for one issue:
Parts of the system need to change depending on the selected TreeNode, which works fine using the AfterSelect event.
However, sometimes the TreeView will get cleared completely resulting in an empty selection which does not trigger this event.
At the momemnt I am calling the event callback manually to fix this issue.
This is obviously dangerous, since I will forget to call this function somewhere. Is there a "correct" way to do this?
Thank You!
This is by design. The underlying native Windows controls only generate notifications for things you cannot figure out yourself. The ListBox control for example doesn't have any event that tells you an item got added or removed. Which is because there is no way for the user to add or remove items. Similarly, there's no way for the user to remove the nodes from a tree view.
These kinds of changes requires code that you write. Since it is your code, you cannot not know that these changes happened. If you want an event then you'll have to raise it yourself. Beware that this is harder than it looks, the TreeNodeCollection class doesn't reliably let you generate an event for programmatic changes to the node collection. It doesn't behave like an ObservableCollection. You are definitely better off by not needing this event.

WPF - Why isn't Keyboard.Focus() working?

have a TextBox item (MyTextBox) on a TabItem control. I have code that looks as follows:
MyTextBox.Focus();
Keyboard.Focus(MyTextBox);
When I run this code through the debugger I see the following after the lines are executed:
MyTextBox.IsFocused = true
MyTextBox.IsKeyboardFocused = false
Can anyone tell me why the textbox isn't receiving keyboard focus? It's just a standard TextBox control that is enabled.
When you try to set Focus to an element besides the things enumerated above by our coleague, you must also know that WPF does not allow cross threaded operations.
In some cases this exception is not raised like in the Focus method call case. What I've done to fix this issue is to call all the code that involves Keyboards focus in an action.
This action is ran inside the control dispatcher to make sure that my code is not being executed from another thread than the UI thread (e.g. timer event or an event raised from another thread):
[UIElement].Dispatcher.BeginInvoke(
new Action(
delegate{
/// put your Focus code here
}
)
);
MyTextBox.IsKeyboardFocused is false because you are looking at it under debugger and the keyboard focus is probably in your Visual Studio... Try debugging focus without breakpoints (e.g. Debug.Write or trace brakepoints) to see actual values of MyTextBox.IsKeyboardFocused in runtime.
Also notice that Focus() method returns boolean value that indicates whether focus was successfully set. Does it return False in your case? If yes, I would suggest stepping into Focus() method in order to find out what is wrong.
3 important properties must be true: IsVisible="True", Focusable="True". IsEnabled="True".
To be focusable, Focusable and IsEnabled must both be true.
http://msdn.microsoft.com/en-us/library/system.windows.uielement.focus.aspx
The accepted answer here does not solve the problem of textboxes who dont gain focus, no matter what the debugger tells you. If you have and can write to your textbox, then you have it keyboard-focused.
I found this here solving the problem (and actually gaining focus, not just settings the values so it looks like focus in the debugger), it comes very close to Pavlov's answer but with the "Focus code" : Keyboard.Focus does not work on text box in WPF
This worked for me (had to do UpdateLayout, otherwise Focus() didn't work immediately after changing tab from script)
tabControl.SelectedIndex = 2;
this.UpdateLayout();
txtMyTextBox.Focus();
It's important where your first two lines of code are executed.
If they are in an event handler that relates to the user pressing a key, using the mouse, altering the visibility of a control, or otherwise taking an action that might have an impact on focus, I find manually calling Focus() often doesn't work.
My theory is that internally, WPF operates as follows:
User or code takes action which could have an impact on focus, e.g. a TextBox control becomes enabled inside a focus scope which previously had no focusable control.
WPF notifies various event handlers, including yours which calls Focus().
WPF updates focus based on the state changes in step 1. This overrides whatever you did in step 2.
That is why this answer suggests to call your Focus() in a queued callback which will be executed after step 3.
Side note: you don't need to call both UIElement.Focus and Keyboard.Focus since the first includes the second (at least if you trust the Microsoft docs).
In conclusion, replace your first two lines of code with this:
// using System.Windows.Threading;
Dispatcher.BeginInvoke(DispatcherPriority.Input, MyTextBox.Focus);

Is there a way of undoing a selection a user makes with the combo box?

In WPF 3.5, is there a property of the combo box will allow the user to undo the selection they've made?
Code
If you look to a way to reset the selection from code (you wrote a property), try the following:
cboYourCombo.SelectedIndex=-1
or
cboYourCombo.SelectedItem=null;
Keyboard Shortcut
If you look for a keyboard shortcut to reset, I've never seen. But if you want, you can do it on your own, it's probably easy:
Attach an EventHandler to the PreviewKeyDown-event of your combobox (or register a general event-handler that works for all comboboxes in your window/app), check the key and if its the key you want to reset, use the code above to reset the selection. Please note, in the PreviewKeyDown-event you can also check for special-keys such as the control-key.
Provide an empty Value
However I think, better would be to add an empty entry and then preselect this empty value. If the user has changed the selection and wants to reset, he can select the empty value. Otherwise you change the standard UI-behaviour and not all people like this.
What do you mean by "undo"? Do you mean something like CTRL+Z (or an undo button), or something like CANCEL? Implementing true undo/ctrl+z on a combo box is something very few applications do, and it will surprise the user. This is a very bad idea, unless you have a very good reason.
If you have a very good reason to go against the design of most windows apps, you can add a handler for SelectionChanged, and implement your own history. Then, if the user either uses a keydown (ctrl+z), or clicks an "undo" button, you can set the selection yourself.
Alternately, if you don't really want an UNDO feature, and actually want a CANCEL feature (a common feature in UI apps), then you shouldn't worry about each control individually. Just keep a set of stored settings (in some custom class), and set all the controls back to the values that were stored. In the case of a combo box, you'd want to set the Selection property.

Commands are disabled in WPF application when data source is empty

I have a simple WPF with a menu, a toolbar and a ListView in a GridView. The menu and toolbar actions are bound to commands.
I have <CommandBinding> that defines when commands should be executed (CanExecute). Some commands, such as "Create New Item" should always be executed, so they are bound to a "e.CanExecute = true;" function.
However, when the user selects all items in the list using Ctrl-A, and then presses Delete, my application runs a BackgroundWorker that deletes the items from the server and then sets the ItemsSource of the list view to the new data collection, which is empty.
This sometimes causes all commands in the menu and toolbar to be disabled. Note that their keyboard shortcuts still work, but the actions are disabled in the menu and toolbar.
This doesn't always happen, and I failed to find a rule to when it does happen.
Did anyone run into a similar behavior or has any idea what might cause it?
Thanks.
Without code it may be hard. It looks like CanExecute() is not called. Set breakpoint in it and check. I assume you are using RoutedCommands. If yes, the problem may appear because MenuItem.CommandTarget is not set, and WPF tries to find CommandBindings somewhere up the tree, beyond your actual command bindings. If this is the case, set CommandTarget to proper value. Also you may want to call CommandManager.InvalidateRequerySuggested() and see what happens.

Resources