How to get FrameworkElement properties before its unloading - wpf

It is need to realize UI settings system - loading/saving some properites of UI elements (which can be modified by user in runtime) from/into persistent storage. For example:
DevExpress grid control - columns width's, visibility, summary area and so on (this control has set of methods pairs like RestoreLayoutFrom/SaveLayoutTo, but SaveLayoutToStream(Xml) doesnt work in grid.Unloaded handler - when grid is disconnected from PresentationSource)
grid rows/columns, which widths/heights can be adjusted by user via GridSplitter
sizeable popup controls
It is easy to set up controls properties from settings storage after controls loading/initializing/etc, but how catch the moment before controls unloading (when they still remains in visual tree) in order to retrieve their settings for saving?
Short description
I intend to create singleton - UISettingsManager, which inside has a Dictionary with pairs of [element Uid, element settings data]. In visual container (Window, UserControl) this manager can be used in a way like this:
public partial class PageHeader : UserControl
{
public PageHeader()
{
InitializeComponent();
UISettingsManager.RestoreSettings(myGridControl);
UISettingsManager.RestoreSettings(myPopup);
}
}
myGridControl & myPopup has unique Uid's (in application scope), so UISettingsManager can retrieve their settings from inner dictionary & apply it to the controls; of course UISettingsManager knows, how to work with some different types of controls.
But when it is the right moment to store settings of controls, which container is Window or UserControl?

I would use the Window's Closing event.
public class MyWindow : Window
{
public MyWindow()
{
this.Closing += new System.ComponentModel.CancelEventHandler(MyWindow_Closing);
}
void MyWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
// Save what I want to here
}
}
This would be the safest bet, because you will always, at some point, close the window.
However, there may be alternatives, including the Unloaded event for a user control.

Related

How do I make user controls for both ListView and ListViewItem work with each other?

I have all the styling, triggers, etc. down for ListView and ListViewItem, and I want to turn them into user controls. How do I make sure that these two "match up" with each other, so that MyListView accepts MyListViewItems as content? Also, considering that I must end the ListView tag by the end of the user control XAML file, I am not sure how I would add items to it.
If you want them to be reusable with different data sets, especially through binding, you should stay away from UserControls and just make custom controls derived from the original types. In that case you create a standalone MyListView.cs and MyListViewItem.cs and all of the XAML for the controls goes into default Styles (usually also containing a ControlTemplate) in Themes/Generic.xaml. You can see an example of this setup by just adding a WPF Custom Control to your WPF project from Add New Item.
Once you've created the .cs files for your custom controls you just need to override a few methods from the base ItemsControl to use MyListViewItem as the item container control. The ListView would end up like this:
public class MyListView : ListView
{
static MyListView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyListView), new FrameworkPropertyMetadata(typeof(MyListView)));
}
protected override DependencyObject GetContainerForItemOverride()
{
return new MyListViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is MyListViewItem;
}
}
You can now use your custom MyListView exactly as you would a normal ListView, including binding to ItemsSource.
Inheritance should take care of that for you. In other words, if you have two user controls, the first one with a basic element of ListView (not UserControl) and the other of ListViewItem (again, not UserControl), and you make sure they extend ListView and ListViewItem respectively in the .cs code, the following should work equally:
ListView lv = new ListView();
lv.Items.Add(new ListViewItem());
or
MyListView mlv = new MyListView();
mlv.Items.Add(new myListViewItem()); //If your myListView extends ListView, and myListViewItem extends ListViewItem in your user control files, of course
In case you are looking for a XAML solution, you should import your namespace at the top
xmlns:myControls="WhateverYourNamespaceAndAssemblyAre"
and on you page/window/whatever
<myControls:myListView>
<myControls:myListViewItem/>
<myControls:myListViewItem/>
</myControls:myListView>

WPF: Setting Keyboard Focus in a User Control? (Problems with KeyBinding)

I have an app that has a main window that contains a bunch of stuff. From time to time the user will do something in response to which I want to display something else entirely in the main window, temporarily hiding what is there.
I'm doing this by making the outermost element in the main window a Grid with no rows or columns defined. Every element in the grid, then will completely fill the one single cell in the grid, drawing on top of the others.
My regular bunch stuff, then, is in the first element of the grid, and my temporary something else is a UserControl as the second element of the grid, that is normally set Visibility=Collapsed.
Except for the KeyBinding everything works fine. When the appropriate command is triggered in the regular bunch of stuff, the visibility on the UserControl is set to Visible, and it covers the regular bunch of stuff completely. When the user clicks on the close button on the UserControl, it's visibility is set to Collapsed, again, and it disappears and the regular bunch of stuff is revealed.
My problem is with KeyBindings. I have a few defined on the UserControl - that should not be defined on the main window - and they don't work. Or rather, they work fine once I click inside the UserControl, but they don't work until I do.
I need them to work as soon as the UserControl is made visible, without requiring the user to click or tab into the UserControl proper.
My guess is that this has something to do with keyboard focus - but I've been unable to find a way to set the focus on the UserControl. Here's the thing - the only element within the UserControl is a tab control, all of the tabs of which are dynamically constructed via templates. There are no elements known at compile time that I can reference explicitly and pass to KeyBoard.Focus().
So, am I right in thinking that it's lack of focus that is causing the problem? And if so, how can I set focus to an element in a TabControl, when I don't even know how many tabs there are, let alone which is the selected one?
I wanted this control to have the focus, when it became visible. So in the constructor, I set up a handler on IsVisibleChanged.
public MyControl
{
...
this.IsVisibleChanged += new DependencyPropertyChangedEventHandler(MyControl_IsVisibileChanged);
}
void MyControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (!(bool(e.NewValue)
return;
this.Focusable = true;
Keyboard.Focus(this);
}
I could have set Focusable in the xaml, but I prefer it in the code-behind, so that all of the relevant code is in one place.
For me this worked in the code-behind:
using System.Windows.Input;
namespace MyApplication.Views.Dialogs
{
public partial class MyControl
{
public MyControl()
{
InitializeComponent();
Loaded += (sender, args) =>
{
MyButton.Focus();
Keyboard.Focus(MyButton);
};
}
}
}

Prism, Unity, and Multiple Views ala MDI

I'm trying to create an application similar to Visual Studio in that we have a main content area (i.e. where documents are displayed in a TabControl, not a true MDI interface), with a menu on the side.
So far, I have everything working, except the content. My goal is that when a user double clicks on an item in the navigation menu on the side, it opens the document in the Content region. This works, but every time I double click it spawns a new instance of that same view. There's a chance that I could have multiple views of the same type (but different "names") in the TabControl content container.
Right now, my code looks something like this...
IRegion contentRegion = IRegionManager.Regions[RegionNames.ContentRegion];
object view = IUnityContainer.Resolve(viewModel.ViewType, viewModel.UniqueName);
if (!IUnityContainer.IsRegistered(viewModel.ViewType, viewModel.UniqueName))
{
IUnityContainer.RegisterInstance(viewModel.UniqueName, view);
contentRegion.Add(view);
}
contentRegion.Activate(view);
However, it appears that the view is never registered, even though I register it... I imagine I'm probably doing this wrong -- is there another way to do this? (re: the right way)
So, the problem was trying to do it this entire way. The smart method (for anyone else trying to do this) is to make use of Prism the correct way.
What I ended up doing was instead Navigating by:
1. In the Navigation Menu, constructing a UriQuery (included in Prism) with the UniqueID of the view I want to display (which is guaranteed to be unique) and adding that to the View I wanted to navigate to, i.e.:
IRegionManager.RequestNavigate(RegionNames.ContentRegion, new Uri(ViewNames.MyViewName + query.ToString(), UriKind.Relative));
where query is the UriQuery object.
2. Register the View and ViewName in the Module via:
IUnityContainer container = ServiceLocator.Current.GetInstance<IUnityContainer>();
container.RegisterType<object, MyView>(Infrastructure.ViewNames.MyViewName);
3. In the View, make sure the ViewModel is a parameter on the constructor. Let Prism inject this manually for us. Inside the constructor, make sure you set the DataContext to the incoming ViewModel.
4. Finally, make sure your ViewModel implements INavigationAware interface... This is a very simple implementation of it (UniqueID is a property on the ViewModel):
public virtual bool IsNavigationTarget(NavigationContext navigationContext)
{
if (navigationContext.Parameters != null)
return (navigationContext.Parameters["UniqueID"] == UniqueID);
return false;
}
public virtual void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public virtual void OnNavigatedTo(NavigationContext navigationContext)
{
if (navigationContext.Parameters != null)
UniqueID = navigationContext.Parameters["UniqueID"];
}
From here, Prism will ensure that only one view of your "UniqueID" will exists, while allowing for others of the same view, but different ViewModel (or data for that ViewModel, i.e. viewing two users in different tabs, but both use the same templated view).

Replicating WPF TabControl selected TabItem design time behavior for a custom control

I'm developing a custom control which shows an inline popup window and I would like to use a similar technique as the TabControl employes so that only popup windows that are selected within the designer or more commonly by placing the cursor within the popup declaration in XAML that it is visualized right within the desiger without having to run the application or change any runtime values by hand.
I've started by duplicating the implementation of the TabControl which I have successfully mimicking everything but it is all copied from Reflector output and Stylesnooper. I've renamed all of the control parts and then replaced the default templates so that the main control uses an ItemsPresenter instead of a ContentPresenter to show the individual popup controls within a Grid panel overlayed on top of one another. So far this is working great too. The problem is that somewhere along the line I lost the ability to have the designer follow the item that is selected in the XAML editor.
Either an explanation of how the TabControl's design time behavior functionality actually works to describe the selected TabItem behavior that I described above or just some pointers on how one could achieve what I'm tryign to do would be great.
To solve a similar problem, I had to create design time support for my custom tab control. Here is a link for WPF Designer Extensibility.
Basically, I created a PrimarySelectionAdornerProvider to handle click interaction and a FeatureConnector<> / FeatureProvider pair for selection changes (including selection changes made in the xaml editor).
The feature provider / connector:
[FeatureConnector(typeof(AutoTabPageSelectionFeatureConnector))]
class AutoTabPageSelectionFeatureProvider : FeatureProvider
{
public AutoTabPageSelectionFeatureProvider()
: base()
{
// sole purpose is to register the connector
}
}
class AutoTabPageSelectionFeatureConnector : FeatureConnector<AutoTabPageSelectionFeatureProvider>
{
public AutoTabPageSelectionFeatureConnector(FeatureManager manager)
: base(manager)
{
SelectionOperations.Subscribe(this.Context, SelectionChanged);
}
private void SelectionChanged(Selection selection)
{
if (selection.PrimarySelection != null)
{
// navigate tree to find parent (custom tab page and custom tab control)
for (ModelItem item = selection.PrimarySelection; item != null; item = item.Parent)
{
// once found, select appropriate tab
}
}
}
}
Edit (more info):
This Microsoft link has a number of links to walk-throughs that should help. Here are the basic steps to get started:
Create a new project, MyAssembly.VisualStudio.Design.dll.
The library should compile to the same location as MyAssembly.dll (important).
Add references to Microsoft.Windows.Design.Extensibility and Microsoft.Windows.Design.Interaction.
Add a reference to your control library.
Create a class called Metadata
Code:
internal class Metadata : IProvideAttributeTable
{
// Accessed by the designer to register any design-time metadata.
public AttributeTable AttributeTable
{
get
{
AttributeTableBuilder builder = new AttributeTableBuilder();
// Add the adorner provider to the design-time metadata.
builder.AddCustomAttributes(
typeof(MyControl), // rename to your control's name
new FeatureAttribute(typeof(MyPrimaryAdornerProvider)), // rename to whatever you will call your PrimaryAdornerProvider
new FeatureAttribute(typeof(AutoTabPageSelectionFeatureProvider)) // rename to whatever you will call your SelectionFeatureProvider
);
return builder.CreateTable();
}
}
}
Create a class MyPrimaryAdornerProvider from PrimarySelectionAdornerProvider (rename to whatever you want). See link for good walk-through.
Create the AutoTabPageSelectionFeatureProvider and AutoTabPageSelectionFeatureConnector from the example above.

WPF UI Scenario - Best way to add a functionality in 50 views?

I want some suggestions to implement this functionality with a neat design and without any code replication. I have an application with many views and grid control in most of the views. I need to add an export functionality (export records to excel).The grid control supports this OOB, just need to call 'Grid.Export()'. I am planning a UI button on the side of every grid and call this method.
So, obviously I need to write the code in code-behind only since I need the control's instance to invoke the method. But, I like to keep the code in one place and somehow invoke the code from all Xamls. (all WPF views).
One technique is to write a BaseView class and derive all Views from this.
But would like to know if WPF suppots any techniques by which I can achieve this. (behaviours etc..?)
Thanks,
Mani
Create a UserControl that includes both the datagrid and the export button. In effect, make it part of the grid itself.
Use this UserControl instead of the default datagrid in all of your views, and you're done.
Furthermore, if you ever have to modify the look and feel of your button or its behaviour, you have only one place in which to change it, and it will be updated in all of your views.
One of solutions is to use WPF routed command.
Note: I wrote this answer with the assumption that your "View" is a subclass of Window class.
First, add a custom routed command to your project.
public static class MyCommands
{
private static readonly RoutedUICommand exportCommand = new RoutedUICommand("description", "Export", typeof(MyCommands));
public static RoutedUICommand ExportCommand
{
get
{
return exportCommand;
}
}
}
In each View, set your custom command to Button.Command and bind a target object to Button.CommandTarget.
<Button Command="local:MyCommands.ExportCommand" CommandTarget="{Binding ElementName=dataGrid1}">Export</Button>
Firnally, in your Application class (named App by default), register a command binding between your custom command and Window.
public partial class App : Application
{
public App()
{
var binding = new CommandBinding(MyCommands.ExportCommand, Export, CanExport);
CommandManager.RegisterClassCommandBinding(typeof(Window), binding);
}
private void Export(object sender, ExecutedRoutedEventArgs e)
{
// e.Source refers to the object is bound to Button.CommandTarget.
var dataGrid = (DataGrid)e.Source;
// Export data.
}
private void CanExport(object sender, CanExecuteRoutedEventArgs e)
{
// Assign true to e.CanExecute if your application can export data.
e.CanExecute = true;
}
}
Now, App.Export is invoked when user click a button.
Sample is available here.

Resources