Disabling all but one child control in a WPF window - wpf

I have a bunch of controls on my window. One of them is a refresh button that performs a cumbersome task on a background thread.
When the user clicks the refresh button, I put the cursor in a wait (hourglass) status and disable the whole window -- Me.IsEnabled = False.
I'd like to support cancellation of the refresh action by letting the user click a cancel button, but I can't facilitate this while the whole window is disabled.
Is there a way to do this besides disabling each control (except for the cancel button) one by one and then re-enabling them one by one when the user clicks cancel?

You can put all the controls in one panel (Grid, StackPanel, etc.), and leave the cancel button in another panel. Then set the IsEnabled property of the other panel.
In practice, this will probably introduce more than one additional panel.
For example, if you had a StackPanel of buttons, you can add an additional StackPanel:
<StackPanel Orientation="Horizontal">
<StackPanel x:Name="controlContainer" Orientation="Horizontal">
<!-- Other Buttons Here -->
</StackPanel>
<Button Content="Cancel" />
</StackPanel>
Then, you would do the following to disable everything but the cancel button:
controlContainer.IsEnabled = false;

I also wanted the user to be able to cancel out of loading.
I found a lovely solution.
foreach (Control ctrl in this.Controls)
ctrl.Enabled = false;
CancelButton.Enabled = true;
This also allows the main window to be selected and moved unlike this.Enabled = false;
which completely locks up the window.

You can data bind each controls IsEnabled property to your custom boolean dependency property that signals when your application is in lock down. Just don't bind the cancel button.
As Donnelle mentioned You can setup multi binding with a converter. Here are a couple examples you can refer to.
WPF MultiBinding with Converter
Implementing Parameterized MultiBinding Sample

Related

WPF MVVM is there any way to switch views without pressing a button?

I'm extremely new to WPF and the MVVM ideology, and am in the process of creating an app with it. So to my understanding, from reading this WPF-MVVM Pattern Guide, we have:
a Window, which facilitates the navigation between Views via Relay Commands,
some Views, which are XAML files that, when interacted with in a certain way (like pressing a
button), uses a Command to call a method in
our ModelViews, which handle most of the logic of the View.
To switch from View1 to View2, in View 1 we might have a Command attached to a button that tells us to load View2,
VIEW1
<StackPanel>
Button<
Command="{Binding DoSomeWorkInModalView1}"
/>
</StackPanel>
<StackPanel Visibility="{Binding VariableInModalView1ThatNeedsToBeSet, Converter={StaticResource BooleanToVisibilityConverter}}">
Button<
Command="{Binding MethodThatLoadsView2}"
/>
</StackPanel>
MODALVIEW1
DoSomeWorkInModalView1(){
//Doing some work here...
VariableInModalView1ThatNeedsToBeSet = true;
}
or something to that effect. After we press a button, ViewModal1 does some work and sets VariableInModalView1ThatNeedsToBeSet to true, so we can see our button and can now press it to go to View2. Great, all of this works in my application.
What I need to do is when this StackPanel becomes visible, call MethodThatLoadsView2 without having to press a button. In other words, as soon as VariableInModalView1ThatNeedsToBeSet is set to true, we load View2 instead of waiting on the user to press a button, kind of like how in React, useEffect() can be used to perform some logic as soon as the component is loaded.
Sorry if this is a little vague, let me know if you have any questions. Thanks!

WPF, what kind of controls will handle MouseLeftButtonDown event?

I have a DataGridControl,and its cell's DataTemplate overwritten to TextBoxs,by clicking outside the cells but still on the DataGrid, I want the TextBox to lose Keyboard focus so that it can Commit the change, but it seems the DataGrid won't handle the MouseLeftButtonDown event, so I have to manually add a handler to the Grid,and in the handler:
e.Handled = true;
Keyboard.Focus( sender as UIElement );
to make the parent panel "focusable".
By using Snoop, I notice that controls like TextBox, Button are capable of handle MosueLeftButtonDown event, while Panels are not,event if set "Focusable" property to "True". Does anyone know the reason behind this, Thanks.
To simplify the situation: suppose we have a TextBox and a Button on a Grid:
<Grid Background="AliceBlue">
<TextBox Height="25" Margin="50" Text="abcd"/>
<Button Height="25" Margin="50,100,50,0"></Button>
</Grid>
when I click on the TextBox, it gets KeyBoard focus, when I click the blank area of the Grid, I want the TextBox to lose focus, the problem is Grid is not focusable compared with TextBoxes and Buttons.
The documentation for Panel shows that Panel can handle mouse events:
https://msdn.microsoft.com/en-us/library/system.windows.controls.panel_events(v=vs.110).aspx

Catching right mouse click on user control level

<UserControl>
<Grid>
<!-- multiple controls here -->
</Grid>
</UserControl>
As the above example says, there are multiple MS / 3rd party controls hosted on UserControl. I need to catch mouse right click over the UserControl (or any of its child controls). It seems that PreviewMouseRightButtonUp event is not fired when clicked on some of the 3rd party controls inside the UserControl. As per documentation, PreviewMouseRightButtonUp is not a tunneling event, but a direct event, so it is possible that some 3d party controls do not notify the subscribers about this event.
I have also tried to add handler to this event, but still no result
AddHandler(UserControl.PreviewMouseRightButtonDownEvent, new RoutedEventHandler(GetHandledToo), true);
AddHandler(UserControl.PreviewMouseRightButtonUpEvent, new RoutedEventHandler(GetHandledToo), true);
AddHandler(UserControl.MouseRightButtonDownEvent, new RoutedEventHandler(GetHandledToo), true);
AddHandler(UserControl.MouseRightButtonUpEvent, new RoutedEventHandler(GetHandledToo), true);
So, is there a way to always catch the right mouse button event on the user control level, no matter if it is being marked as handled or not?
Edit: I have found where the problem lies. The problem lies in the ContentControl which has not yet evaluated its content. Example:
<ContentControl x:Name="chart" Content="{Binding DiagramScreen}" />
ContentControl recolves its structure through DataTemplate. If DiagramScreen is NULL, the ContentControl's content is not yet created. This means that the space that this ContentControl occupies in UserControl is not responding to Mouse events. How Can I make ContentControl respond to mouse events, even if its content is NULL?
I think if it is handled you can't get it further for bubbling event. A workaround is to pass your host object to your user control. After that, you can call whatever method you want from your host object. It's ugly but I think it will work

How can I tell when the default textbox context menu is about to open or close?

How can I tell when the default textbox context menu is about to open (before it opens) or after it has closed (not before, after)? The ContextMenuOpening and ContextMenuClosing events don't seem to fire when I'm using the standard, built-in menu. I'm guessing I could simply recreate the menu and populate it with standard commands, but that does seem to be a bit overkill.
The reason for this specifically, is I have a templated control that has a textbox swapped in when in 'Edit' mode. That control automatically drops out of edit mode when the textbox loses focus. Problem is when the context menu pops up, the textbox loses focus, and thus it drops out of edit mode, and the context menu disappears instantly.
What I want to do is just before the context menu opens, set a flag to short-circuit LostFocus event code on the textbox. Then after the context menu closes, I need to clear that flag but I also need to detect if the control that now has the focus is still the textbox, and if not, then process the code as if it did lose focus. (Alternately I could test an event prior to it closing if I knew which control will have the focus once it does close. It would achieve the same effect.)
This is needed to handle the specific case if someone shows the context menu (and as such the textbox technically doesn't have focus anymore) but then clicks elsewhere in the UI which dismisses the context menu, because I then need to detect that the textbox has in fact lost focus and as such the control should drop out of edit mode. But if the user dismisses the context menu by clicking back in the textbox, then I don't want that LostFocus event to fire.
Make sense?
M
UPDATE: Technically this question wasn't answered although I marked it as such since the responders did help me solve my problem. But as for the actual question here, it looks like the answer is 'You can't'.
The good news is since the default textbox context menu is just three standard items, it's easy to duplicate by adding this to the resources somewhere...
<ContextMenu x:Key="DefaultTextBoxContextMenu">
<MenuItem Command="ApplicationCommands.Cut" />
<MenuItem Command="ApplicationCommands.Copy" />
<MenuItem Command="ApplicationCommands.Paste" />
</ContextMenu>
...and attach it like this...
<TextBox x:Name="EditTextBox"
ContextMenu="{StaticResource DefaultTextBoxContextMenu}"
ContextMenuOpening="EditTextBox_ContextMenuOpening"
ContextMenuClosing="EditTextBox_ContextMenuClosing" />
...then your eventing works as you would expect. Still odd if you ask me, but a trivial work-around anyway so I won't complain.
M
When the textbox's context menu opens the textbox loses keyboard focus, but retains logical focus. In other words, its LostKeyboardFocus event will fire but its LostFocus event will not. The textbox's IsFocused property remains true when the context menu is open. Based on your description of the problem, you should be able to rely on logical focus to determine when to end "edit mode" in your control.
By default a TextBox still reports true for IsFocused while the default context menu is open but reports false for IsKeyboardFocused. That is to say, LostFocus is not raised on the TextBox when the context menu is opened but will be raised if the context menu is closed by selecting some other control. This sounds like precisely the behavior you are looking for.
You can show this default behavior with a small test program:
<Grid>
<StackPanel>
<TextBox Text="Some text one"
GotFocus="TextBox_GotFocus"
LostFocus="TextBox_LostFocus"/>
<TextBox Text="Some text two"/>
</StackPanel>
</Grid>
and the code-behind:
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
Debug.WriteLine("GotFocus");
}
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
Debug.WriteLine("LostFocus");
}
If you are not getting this default behavior in the context of your larger application, then there might be a focus scope issue interfering.

Set Shortcut key for ToggleButton with Image content

I've created a ToggleButton to show and hide a piece of UI and I've sent its Content to an icon.
I now want to add a shortcut key to the ToggleButton but I'm unsure how to do with without binding a command as well. Since all I am doing is binding to the IsChecked state, I don't need a command to do any other functionality and creating an empty one seems incorrect.
Here is my ToggleButton as it stands currently non-functional and not responding when I press the indicated shortcut key.
<ToggleButton ToolTip="Command History"
MinWidth="24"
IsChecked="{Binding IsShowHistoryChecked}"
Margin="7">
<ToggleButton.InputBindings>
<KeyBinding Gesture="Ctrl+H" />
</ToggleButton.InputBindings>
<Image Source="/Amuse;component/Images/ComHistory256.png"
Width="24" />
</ToggleButton>
An InputBinding on the ToggleButton itself won't do the trick, but there are two good WPF solutions for your problem:
Use a RoutedCommand that updates the model.
Register an access key.
Why your InputBinding solution won't work
The InputBinding you have defined currently won't work because it doesn't list a command. It is easy to create a command that toggles a button, as follows:
public void Execute(object parameter)
{
((ToggleButton)parameter).IsChecked = !((ToggleButton)parameter).IsChecked;
}
However this will not achieve what you are looking for. You want Ctrl-H to toggle your button even when the button is not focused. An InputBinding will not accomplish this for you, since it only works when the button has focus. I will now discuss two solutions you can use.
Option 1: Use a RoutedCommand that updates the model
The whole point of WPF's architecture is that you never will need to "toggle a button" in the first place: Conceptually all keyboard and mouse actions in WPF should serve to toggle a bound property in your model or view model. The ToggleButton then just becomes the mechanism for accepting mouse clicks, but need not be the only one.
The name you chose for your "IsShowHistoryChecked" property indicates a fundamental problem in the way you're conceptualizing your view model. Your view model should not be designed around the view - rather, it should expose logical concepts such as a "ShowHistory" property. The view may bind this to a CheckBox or ToggleButton, or it may choose some other mechanism, or it may not expose it at all. The whole point of data binding and view models is that when you create the view model you don't care what the actual view will be like. In fact, during automated unit testing there will be no checkbox so "IsShowHistoryChecked" would clearly be a real misnomer.
So let's say you've properly separated your view from your view model and you have a "ShowHistory" property. First implement a "ToggleShowHistory" command in your view model that, when executed, toggles the ShowHistory property. Now all you have to do is assign this command an InputBinding of Ctrl-H at the view level and you're done. Even if the ToggleButton is removed from the view entirely the InputBinding will still take effect and Ctrl-H will still work. Nirvanna.
Option 2: Register an access key
Windows has a standard mechanism for associating keys with arbitrary buttons and labels, which is the "access key" concept. If you register an access key of "h" on the ToggleButton, pressing Alt-H will toggle the button, and so will just plain H if you don't have a TextBox or another control accept it first.
It is very simple to register the access key in code:
AccessKeyManager.Register("h", togleButton);
This registers "h" as the access text. Now if the user presses Alt-H anywhere in scope (or plain "h" if isn't handled by a TextBox), your button will toggle.
You can also do it in XAML. If you're showing text in your button, just use an underline before the access key letter:
<Button Text="Show _History" ... />
If you're showing something other than just text, include a hidden AccessText element in your button content:
<Button ...>
<Grid>
<AccessText Text="_h" Visibility="Collapsed" />
<Image ...>
</Grid>
</Button>
In case you're wondering, WPF has no built in mechanism to request that AccessKey registrations respond to Ctrl instead of Alt, so this will not allow you to set Ctrl-H as the access key.

Resources