WPF - Detect when UserControl is not visible anymore and prompt user - wpf

So, I have a class, which goes as follows:
public class EditorUserControl : UserControl
{
public EditorUserControl()
: base()
{
this.IsVisibleChanged += new DependencyPropertyChangedEventHandler(
EditorUserControl_IsVisibleChanged);
}
void EditorUserControl_IsVisibleChanged(
object sender,
DependencyPropertyChangedEventArgs e)
{
if (IsEditing && !((bool)e.NewValue))
{
PressedButton pressedButton = PromptUser(new Buttons[] {
"Save changes to the object you just edited?",
Buttons.Yes,
Buttons.No,
Buttons.Cancel });
if(pressedButton == Buttons.Cancel)
{
CANCELTHETHING();
}
}
}
}
In words - this class is a base for all entity editing controls and when it goes invisible (e.g. window is closed, tab changed etc.) I need to check if the user has made changes and prompt the user whether to save/discard/cancel. The save/discard are easy. The problem is with the third option (and it must be there) - I cannot figure out a way how could I cancel the source event that caused the visibility to change (as there is no way to get to that actual event). Is there a better way to do this functionality (that would not require signing up for all of the possible sources of events)?

I don't think it is possible to cancel the source (event) as you want to.
Consider this line of code - EditorUserControl.Visibility = Visisibility.Hidden;
This will also cause the IsVisibleChanged event to fire, but there is no way to cancel a single line of code.
Your only option is to move the logic inside the IsVisibleChanged event handler to a method that will be called as appropriate by the application. For instance if you close the window then in the window_closing event handler you call the method and if the result is Button.Cancel then you cancel the closing event. If you change tabs then you handle a SelectionChanged event and again call the method and if you need to cancel then you set the selected tab index back to the previous value etc.

Related

UpdateSource on active field before deactivating window

I have a program with a main window in which a user may alter data. The program also has a to-do-dialog, in which the user may see a list of stuff to do - and this dialog has it's own datacontext (connection to database) but needs to show let updated data.
I did not set UpdateSourceTrigger in all my fields in the main window to PropertyChanged (this wouldn't be wanted in all cases anyway because of functionality in the main window).
But I need the main window to do a updatesource on the "active" field in case the user activates the to-do-dialog so that the data may be saved to the database and re-read in the to-do-dialog.
If there were a "Deactivating" event on the window, I could use that to simply set focus to something else (forcing whatever currently focussed control to update it source). But no Deactivating event exists, only Deactivated which happens after the fact. And setting focus in the deactivated main window messes up up the activation of the to-do-dialog. Also, I cannot do this in the to-do-dialogs Actived event. A Activating event would have helped - but that does not exist.
Any good ideas how to force updatesource on my main window before the to-do-dialog gets activated (either by tabbing to it or by clicking it with the mouse)?
This kind of goes off of #AkselK's answer, but I find the keyboard focused control using the Keyboard.FocusedElement property and update the right bindings based on whether its a TextBox, Selector, or ToggleButton. I expected the Keyboard.FocusedElement to tell me the window I was swapping to is what has focus, but it actually does give me the control I was in inside the deactivating window.
void MyWindow_Deactivated(object sender, EventArgs e)
{
if(Keyboard.FocusedElement is TextBox)
{
(Keyboard.FocusedElement as TextBox).GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
else if(Keyboard.FocusedElement is Selector)
{
(Keyboard.FocusedElement as Selector).GetBindingExpression(Selector.SelectedItemProperty).UpdateSource();
//todo: should this also update SelectedValueProperty?
}
else if(Keyboard.FocusedElement is ToggleButton)
{
(Keyboard.FocusedElement as ToggleButton).GetBindingExpression(ToggleButton.IsCheckedProperty).UpdateSource();
}
}
I am unsure if the Selector portion should also update the binding for SelectedValue.
You do not need to set Focus to a different element to update the binding. Instead, you can use the GetBindingExpression method:
valueTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
This might work in the Deactivating event.

Setting Default Keyboard Focus On Loading A UserControl

I have an MVVM setup with a mainwindow that contains a ContentControl.
I set this to a particular viewmodel which then maps to a view.
A view is a usercontrol.
I want to be able to set the default keyboard focus to a default element in the usercontrol(View) when it loads so the application can eventually be driven just by using up, down, left, right and enter.
Some of my failed attempts are setting
FocusManager.FocusedElement="{Binding ElementName=DefaultElement}"
in my content control tag. This sets the logical focus but not the keyboard focus
I'd rather keep the solution in xaml if possable but have tried placing the following in code behind.
Keyboard.Focus(DefaultElement);
This does not work but if I popup a message box first it does. I'm a little confused as to why.
MessageBox.Show(Keyboard.FocusedElement.ToString());
Keyboard.Focus(DefaultElement);
EDIT::::
I just placed this in my onloaded event of my user control. It seems to work but can anyone see any issues that might arrise at this priority level. I.E a circumstance when the action will never run?
Dispatcher.BeginInvoke(
DispatcherPriority.ContextIdle,
new Action(delegate()
{
Keyboard.Focus(DefaultElement);
}));
It seems that this wpf the you have to implement a workaround on a case by case basis. The solution that seemed to work best, most of the time for me was to insert the focus code inside the dispatcher when OnVisible was changed. This sets the focus not only when the View/Usercontrol loads but also if you a changing Views by way of Visibility. If you Hide and then Show a ContentControl that is mapped to your ViewModels then the Loaded event won't fire and you'll be forced to Mouse input, or tabbing (Not so good if you want to navigate your app with a remote control).
VisibilityChanged will always fire however. This is what I ended up with for my listbox.
private void ItemsFlowListBox_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue == true)
{
Dispatcher.BeginInvoke(
DispatcherPriority.ContextIdle,
new Action(delegate()
{
ItemsFlowListBox.Focus();
ItemsFlowListBox.ScrollIntoView(ItemsFlowListBox.SelectedItem);
}));
}
}
I had the same symptom for a WPF UserControl hosted in a Winforms application. Just wanted to note I was about to try this solution when I found a normal TabIndex in the Winforms app fixed it
Per How to set which control gets the focus on application start
"The one with the minimum tab index automatically gets the focus
(assuming the TabStop property is set to true). Just set the tab
indices appropriately."
It's a tricky one with no easy answer. I'm currently doing this, although I'm not sure I like it:
public MyView()
{
InitializeComponent();
// When DataContext changes hook the txtName.TextChanged event so we can give it initial focus
DataContextChanged +=
(sender, args) =>
{
txtName.TextChanged += OnTxtNameOnTextChanged;
};
}
private void OnTxtNameOnTextChanged(object o, TextChangedEventArgs eventArgs)
{
// Setting focus will select all text in the TextBox due to the global class handler on TextBox
txtName.Focus();
// Now unhook the event handler, since it's no longer required
txtName.TextChanged -= OnTxtNameOnTextChanged;
}
And in case you're wondering what the global class handler does, it's this:
protected override void OnStartup(StartupEventArgs e)
{
...
// Register a global handler for this app-domain to select all text in a textBox when
// the textBox receives keyboard focus.
EventManager.RegisterClassHandler(
typeof (TextBox), UIElement.GotKeyboardFocusEvent,
new RoutedEventHandler((sender, args) => ((TextBox) sender).SelectAll()));
which auto selects TextBox text when receiving keyboard focus.

using routed events within Silverlight user controls

within my current project file I have a user control that has a storyboard animation applied to the control. When a button is clicked in the page the storyboard starts and basically visually presents the control to the user. The storyboard resides in the current page as a resource
<navigation:Page.Resources>
<Storyboard x:Name="PreferncesOpen">....</Storyboard x:Name="PreferncesOpen">
</navigation:Page.Resources>
Within the page I have button that I have a click event on that starts the storyboard
private void btnOpenPreferences_Click(object sender, RoutedEventArgs e)
{
preferencesPanel.Visibility = System.Windows.Visibility.Visible;
PreferncesOpen.Begin();
}
Within the userControl (preferencesPanel) I have a button that when clicked needs to close/collapse the user control. I plan to do this using Visibility.collapsed. I assume that I need to use routed commands since the button is within the user control but the actions need to be called within the page that contains the control? I'm still new to routed commands and I assume this is the correct approach. I'm just unsure how to click on a button within the user control and have it modify or execute commands that would impact how the page (in which this control resides) may change or for that part affect other elements within the page? For example when the button is clicked within the user control I would like the visibility of the user control to be set to collapsed. I also would like to have the width of one of the grid columns within the main page re-size. I have done this in the past using the code behind for the page but I am trying to separate some of this and I thought routed commands would be the way to go?
I'd greatly appreciate any tips.
Thank you in advance
The title is a bit misleading, you're asking about commands rather then routed events if I understand you correctly.
Here's an example of using a DelegateCommand<T> from the Prism library; It happens to be my personal preference.
Markup :
<Button x:Name="MyButton" Content="Btn" Command="{Binding DoSomethingCommand}"/>
Code-behind* or ViewModel :
(* if you're not using MVVM make sure to add MyButton.DataContext = this; so you're sure that the button can databind to your code behind effectively)
public DelegateCommand<object> DoSomethingCommand
{
get
{
if(mDoSomethingCommand == null)
mDoSomethingCommand = new DelegateCommand(DoSomething, canDoSomething);
return mDoSomethingCommand;
}
private DelegateCommand<object> mDoSomethingCommand;
// here's where the command is actually executed
void DoSomething(object o)
{}
// here's where the check is made whether the command can actually be executed
// insert your own condition here
bool canDoSomething(object o)
{ return true; }
// here's how you can force the command to check whether it can be executed
// typically a reaction for a PropertyChanged event or whatever you like
DoSomethingCommand.RaiseCanExecuteChanged();
The argument that's passed to the above function is the CommandParameter dependency property (in Prism it's an attached property as well as the Command property if memory serves me right).
When it's set, you can pass a value of your choosing to the command that you wish to execute.
Hope that helps.

Delete / Kill / Remove UserControl and its events handlers

I dynamically create UserControls using Reflection:
UserControl myConmtrol = (UserControl)Activator.CreateInstance(t);
The UserControl may handle a Closing event but I do not know the name of the handler.
When the Window hosting the UserControl closes I remove the UserControl from its parent Window and it disappears from the Window: Everything seems OK.
But if I open and close again the UserControl I can see in the debugger the Closing event is handled twice, one time by the current UserControl but also by the previous UserControl that is still alive.
Theorically the UserControl being no longer referenced should be GarbageCollected.
How can I force it to be Killed/Deleted/Disposed ? At least is there a way to forbid it handles events ?
Thanks
Not sure without more detail but i would start and check if you have any event handlers that isn't removed
Do I need to remove event subscriptions from objects before they are orphaned?
Answer about removing handlers without knowing their names:
public void RemoveHandlerOfUserControl(UserControl uc)
{
MulticastDelegate dlg = MyEvent;
Delegate[] handlers = dlg.GetInvocationList();
foreach (Delegate d in handlers)
{
if (d.Target == uc)
{
this.RemoveHandler(MyEvent, d);
}
}
}
This method must reside in the object where the event is declared.
I had to cope with the same situation in Winforms where I dynamically create a user control inside another user control (Let's say a "DynControl" inside "HostControl").
There is no Closing event in "HostControl". So I used the Disposed event of HostControl to release resources :
this.Disposed += (s, e1) =>
{
DynControl.Click -= += new EventHandler(MyClickHandler);
this.Controls.Remove(DynControl);
DynControl.Dispose();
};

Add a Load event for Winforms Control just as Form class

is there a way I can get a Load event for System.Windows.Forms.Control just like System.Windows.Forms.Form.Load?
I want to run some initialize code before the control first shown.
Also, it would be nice to be able to do the same for System.Windows.Forms.ToolStripStatusLabel which is not actually a Control, but works like one.
Ideally, I can do this:
control.OnLoad(() => { dosomething here; });
in which OnLoad is a extension method that would run the argument Action when the "control" "Loads".
Thanks!
Form.Load event is called by the OnLoad method which is called from the OnCreateControl method which belongs to the Control class. So for the form the calling sequence would be following:
OnCreateControl start
OnLoad start
Form Load event call
OnLoad finish
OnCreateControl finish
I guess you can override OnCreateControl for your component and add your optimization code there.
Hope this helps, Regards.
For a control you can override either OnControlCreated or OnHandleCreated. The latter one can fire multiple times if it is necessary to recreate the control window. Be sure to use it if your code affects the window itself. In other words, if you do anything that requires the Handle property.
Few suitable choices for a ToolStripItem derived control. I'd recommend overriding SetVisibleCore() or OnAvailableChanged() or the AvailableChanged event. They run when the Visible property of the ToolStripItem changes. Beware that it may fire multiple times, keep a bool field that tracks that your initialization code has already run.
Last but not least, be sure to only do any of this if your code actually requires the control to be created. The vast majority of init code can go in the constructor. You only need a Load event if your code depends on the actual Location and Size of the control. Which might be different from the designer value if the form rescales itself due to a different system font or video DPI setting on the target machine.
I needed a solution like this for a TabPage within a TabControl.The only thing I came up with was using the paint event handler. I added the event handler for Paint and in the very first line I remove the event handler and then do more initialization code. This only works if you do nothave any custom painting. Alternatively, if you do need to do custom painting you could add a flag to check for each time Paint Executes.
//Paint only runs once
private void tabPage1_Paint(object sender, PaintEventArgs e)
{
tabPage1.Paint -= tabPage1_Paint;
//Do initialization here
}
/////////////////////////////////////////////////////////////////////////////////
//Paint always runs
private bool IsFirstPaint = true;
private void tabPage1_Paint(object sender, PaintEventArgs e)
{
if(IsFirstPaint)
{
IsFirstPaint = false;
//Do initialization here
}
//Do custom painting here
}

Resources