The prism documentation states that there are three region adapters available:
ContentControlRegionAdapter. This adapter adapts controls of type System.Windows.Controls.ContentControl and derived classes.
SelectorRegionAdapter. This adapter adapts controls derived from the class System.Windows.Controls.Primitives.Selector, such as the System.Windows.Controls.TabControl control.
ItemsControlRegionAdapter. This adapter adapts controls of type System.Windows.Controls.ItemsControl and derived classes.
Unfortunately, Panel does not fall into any of those categories, and I want to be able to write this in my .xaml.cs:
<Canvas cal:RegionManager.RegionName="{x:Static local:RegionNames.MainCanvas}">
How can we accomplish this?
The answer to this can be found in this very nice, descriptive blog post.
However, I want the answer stored on StackOverflow as well :) It took a bit of searching to get this from Google. Here is my code that works with a basic Panel.
Step 1 - create a new region adapter
public class PanelHostRegionAdapter : RegionAdapterBase<Panel>
{
public PanelHostRegionAdapter(IRegionBehaviorFactory behaviorFactory)
: base(behaviorFactory)
{
}
protected override void Adapt(IRegion region, Panel regionTarget)
{
region.Views.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement element in e.NewItems)
{
regionTarget.Children.Add(element);
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (FrameworkElement CurrentElement in e.OldItems)
regionTarget.Children.Remove(CurrentElement);
}
};
}
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
}
Step 2 - update your bootstrapper
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
...
}
protected override IModuleCatalog GetModuleCatalog()
{
...
}
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
RegionAdapterMappings Mappings = base.ConfigureRegionAdapterMappings();
Mappings.RegisterMapping(typeof(Panel), Container.Resolve<PanelHostRegionAdapter>());
return Mappings;
}
}
Related
I've a Prism Custom Region Adapter, to display every view in a different tab of our DevExpress "DocumentGroup".
In order to do this, I've the following RegionAdapter:
public class DocumentGroupRegionAdapter : RegionAdapterBase<DocumentGroup>
{
public DocumentGroupRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory)
: base(regionBehaviorFactory)
{
}
protected override void Adapt(IRegion region, DocumentGroup regionTarget)
{
region.Views.CollectionChanged += (sender, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement element in args.NewItems)
{
DocumentPanel documentPanel = new DocumentPanel {Content = element, DataContext = element.DataContext};
regionTarget.Items.Add(documentPanel);
}
}
};
}
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
}
With AllActiveRegion being:
public class AllActiveRegion : Region
{
public override IViewsCollection ActiveViews
{
get { return Views; }
}
public override void Deactivate(object view)
{
throw new InvalidOperationException(Resources.DeactiveNotPossibleException);
}
}
And we were registering several View for this region:
_regionManager.RegisterViewWithRegion(Regions.MainSections, typeof(Views.Layout.RootView));
_regionManager.RegisterViewWithRegion(Regions.MainSections, typeof(Views.Configure.RootView));
_regionManager.RegisterViewWithRegion(Regions.MainSections, typeof(Views.Dashboard.RootView));
It worked fine up until now, but now, on certain options, we need to activate one of the tab. This would be done by calling item.IsActive = true.
How do I specify which item I want to navigate too?
What should I override to set this active item?
For the interested ones, I had to do several things to resolve the issue:
Switch to the SingleActiveRegion instead of the AllActiveRegion. The AllActiveRegion doesn't keep track of the selected(active) item, basically, the region.Views = region.ActiveViews. So you cannot register to ActiveViews changes.
Listen to the region.ActiveViews.CollectionChanged event. When it fires, I've to activate the DevExpress component
Listen to the DevExpress component for when the user clicks on the tabs. This is required, because otherwise Prism might think the control is already active and not active it again.
The code look like this:
public class DocumentGroupRegionAdapter : RegionAdapterBase<DocumentGroup>
{
public DocumentGroupRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory)
: base(regionBehaviorFactory)
{
}
protected override void Adapt(IRegion region, DocumentGroup regionTarget)
{
region.Views.CollectionChanged += (_, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement element in args.NewItems)
{
DocumentPanel documentPanel = new() {Content = element, DataContext = element.DataContext};
regionTarget.Items.Add(documentPanel);
}
}
};
region.ActiveViews.CollectionChanged += (_, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement element in args.NewItems)
{
DocumentPanel existingItem = regionTarget.Items.Cast<DocumentPanel>().FirstOrDefault(i => i.Content == element);
if (existingItem != null)
{
existingItem.IsActive = true;
}
}
}
};
regionTarget.SelectedItemChanged += ((_, args) =>
{
region.Activate(((DocumentPanel)args.Item).Content);
});
}
protected override IRegion CreateRegion()
{
return new SingleActiveRegion();
}
}
Now that I've seen the source code of Prism, I think writing a custom Region could maybe also do the job, I'm not totally sure when the Activate() is called
Im working on gauge control.
I need to redraw everything inside when Size or Padding property is changed.
This is how I deal with Size property changes:
public RoundGauge()
{
this.SizeChanged += delegate
{
ReDrawEverything();
};
InitializeComponent();
}
But there is no PaddingChanged event. What can I do with this?
There is no indeed no "PaddingChanged" event but you could use a DependencyPropertyDescriptor to subscribe to changes to a dependency property:
public partial class RoundGauge : UserControl
{
public RoundGauge()
{
InitializeComponent();
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(PaddingProperty, typeof(UserControl));
if (dpd != null)
dpd.AddValueChanged(this, OnPaddingChanged);
}
private void OnPaddingChanged(object sender, EventArgs e)
{
MessageBox.Show("Padding changed!");
}
}
Please refer to the following blog post for more information.
Handling changes to dependency properties in the view: https://blog.magnusmontin.net/2014/03/31/handling-changes-to-dependency-properties/
You can override OnPropertyChanged event:
public partial class RoundGauge : Control
{
public RoundGauge()
{
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == PaddingProperty)
{
Thickness oldPadding = (Thickness)e.OldValue;
Thickness newPadding = (Thickness)e.NewValue;
// ...
}
}
}
I am writing an application with WPF using Prism and the Managed Extensibility Framework. The purpose is to allow developers to create there own 3rd party modules. To remove as much work as possible I have created some base classes in a Common module which already does most of the required work. (eg. drag n drop for views, adds MEF InheritedExportAttribute). One of these controls is called ModuleControl (derives from UserControl) shown below.
I have everything working great if my module has a view class directly derived from UserControl with one child ModuleControl in the XAML (see the examples below). This seems like a lot extra work for users. I'd like to make my view class derive from 'ModuleControl' just using code. If I do this as in the final example below, then RegisterViewWithRegion throws the following exception:
Activation error occured while trying to get instance of type ModuleView, key \"\"
System.Exception {Microsoft.Practices.ServiceLocation.ActivationException}
I realise this happens when the type can't be registered. So my questions are how can I achieve this? What am I doing wrong? Does RegisterViewWithRegion explicitly expect UserControl and nothing derived from it?
Here is an example of my first control located in Common.dll.
namespace Common.Controls
{
[InheritedExport]
public partial class ModuleControl: UserControl
{
private Point _anchor;
public ModuleControl()
{
InitializeComponent();
}
public ObservableCollection<IComponent> ModuleComponents
{
get
{
return (ObservableCollection<IComponent>)GetValue(ModuleComponentsProperty);
}
set
{
SetValue(ModuleComponentsProperty, value);
}
}
private void InitialiseCollection()
{
try
{
ModuleComponents = new ObservableCollection<IComponent>();
var components = ServiceLocator.Current.GetAllInstances(typeof(IComponent));
Assembly controlAssembly = UIHelper.FindAncestor<UserControl>(VisualTreeHelper.GetParent(this)).GetType().Assembly;
foreach (IComponent c in components)
{
if (c.ExportType.Assembly.Equals(controlAssembly))
{
ModuleComponents.Add(new Component(c.ViewType, c.Description));
}
}
}
catch (Exception e)
{
}
}
public string ModuleName
{
get
{
return (string)GetValue(ModuleNameProperty);
}
set
{
SetValue(ModuleNameProperty, value);
}
}
public static readonly DependencyProperty ModuleComponentsProperty =
DependencyProperty.Register("ModuleComponents", typeof(ObservableCollection<IComponent>), typeof(ModuleControl), new UIPropertyMetadata(null));
public static readonly DependencyProperty ModuleNameProperty =
DependencyProperty.Register("ModuleName", typeof(String), typeof(ModuleControl), new UIPropertyMetadata("No Module Name Defined"));
private void DragList_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Store the mouse position
_anchor = e.GetPosition(null);
}
private void DragList_PreviewMouseMove(object sender, MouseEventArgs e)
{
// Get the current mouse position
Point mousePos = e.GetPosition(null);
Vector diff = _anchor - mousePos;
if (e.LeftButton == MouseButtonState.Pressed &&
Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
{
// Get the dragged ListViewItem
ListView listView = sender as ListView;
ListViewItem listViewItem = UIHelper.FindAncestor<ListViewItem>((DependencyObject)e.OriginalSource);
// Initialize the drag & drop operation
if (listViewItem != null)
{
DataObject dragData = new DataObject("moduleFormat", listViewItem.Content);
DragDrop.DoDragDrop(listViewItem, dragData, DragDropEffects.Move);
}
}
}
private void moduleControl_LayoutUpdated(object sender, EventArgs e)
{
if (ModuleComponents == null)
InitialiseCollection();
}
}
Following from the example A Prism 4 Application Checklist I built the following module modifying this to use MEF instead of Unity where appropriate.:
This module is located in RandomNumbers.dll
namespace RandomNumbers
{
[ModuleExport(typeof(RandomNumberModule))]
public class RandomNumberModule: IModule
{
public void Initialize()
{
var regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
regionManager.RegisterViewWithRegion("MyRegion", typeof(ModuleView));
}
}
}
The ModuleView XAML that works looks like this:
<UserControl x:Name="userControl" x:Class="RandomNumbers.Views.ModuleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:common="clr-namespace:Common.Base;assembly=Common"
xmlns:commonctrls="clr-namespace:Common.Controls;assembly=Common"
mc:Ignorable="d">
<commonctrls:ModuleControl x:Name="moduleControl" ModuleName="Random Number Module" />
</UserControl>
The codebehind for this is:
namespace RandomNumbers.Views
{
[Export]
public partial class ModuleView : UserControl
{
private Point startPoint;
public ModuleView()
{
InitializeComponent();
}
}
}
As already mentioned, all the above code works perfectly. However if I replace the XAML and code behind with this, then I get the exception as described. I have tried leaving out the ExportAttribute but nothing changes.
namespace RandomNumbers.Views
{
[Export(typeof(UserControl))]
public class ModuleView : ModuleControl
{
public ModuleView() : base() { }
}
}
I have derived a custom bootstrapper from Caliburn.Micro.Bootstrapper, I notice it can take a generic type parameter - what is this for?
public class SimpleInjectorBootstrapper : Caliburn.Micro.Bootstrapper
{
private Container container;
public SimpleInjectorBootstrapper()
{
}
protected override void Configure()
{
this.container = new Container();
this.container.Register<IWindowManager, WindowManager>();
this.container.Register<IEventAggregator, EventAggregator>();
this.container.Register<IAppViewModel, AppViewModel>();
}
protected override object GetInstance(Type serviceType, string key)
{
return this.container.GetInstance(serviceType);
}
protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return this.container.GetAllInstances(serviceType);
}
protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
{
base.OnStartup(sender, e);
var appViewModel = this.container.GetInstance<IAppViewModel>();
var windowManager = this.container.GetInstance<IWindowManager>();
windowManager.ShowWindow(appViewModel);
}
}
It's a view model type to use as your starting view model. Caliburn.Micro will resolve the type from the IoC container, and in WPF use the WindowManager to display the root view. Bascially what you're doing in your OnStartup override.
I've already seen some questions like this one, but the docking library I'd like to use must have an important feature, which was not asked: it must support MVVM.
So, among Telerik, DotNetBar, DevZest, and the other libraries around there (excluding AvalonDock, which I have already tested), is there one you actually use with MVVM?
Thanks in advance
Hello Mike try with this:
Easy way: Implement Sofa, An adaptation of AvalonDock for Prism
Using AvalonDock and implementing a custom region adapter like this:
public class ResizingPanelRegionAdapter : RegionAdapterBase<DockingManager>
{
public ResizingPanelRegionAdapter(IRegionBehaviorFactory factory)
: base(factory)
{
}
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
protected override void Adapt(IRegion region, DockingManager regionTarget)
{
region.Views.CollectionChanged += delegate(Object sender, NotifyCollectionChangedEventArgs e)
{
OnViewsCollectionChanged(sender, e, region, regionTarget);
};
}
private void OnViewsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e, IRegion region, DockingManager regionTarget)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (object item in e.NewItems)
{
UIElement view = item as UIElement;
if (view != null)
{
//Get
ResizingPanel resizingPanel = GetResizingPanel(regionTarget.Content);
resizingPanel.Background = Brushes.White;
DocumentPane document = GetDocumentPane(resizingPanel.Children);
//document.Background = Brushes.White;
DocumentContent newContentPane = new DocumentContent();
newContentPane.Content = item;
var itemView = (item as IViewBase);
if (itemView != null)
newContentPane.Title = itemView.Title;
//When contentPane is closed remove the associated region
newContentPane.Closed += (contentPaneSender, args) =>
{
region.Remove(item);
newContentPane.Content = null;
};
document.Items.Add(newContentPane);
if (!resizingPanel.Children.Contains(document))
resizingPanel.Children.Add(document);
regionTarget.Content = resizingPanel;
newContentPane.Activate();
region.Activate(item);
}
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
}
}
private DocumentPane GetDocumentPane(UIElementCollection collection)
{
foreach (object item in collection)
{
var documentPanel = item as DocumentPane;
if (documentPanel != null)
return documentPanel;
}
return new DocumentPane();
}
private ResizingPanel GetResizingPanel(object content)
{
var resizingPanel = content as ResizingPanel;
if (resizingPanel != null)
return resizingPanel;
return new ResizingPanel();
}
}
And your in your XAML you could implement it like this:
<avalon:DockingManager prism:RegionManager.RegionName="MainRegion">
</avalon:DockingManager>
How it works?
Simple, first at all you have to keep in mind that Region adapters are responsible for creating a region and associating it with the control. This allows you to use the IRegion interface to manage the UI control contents in a consistent way.
And a DockingManager is the core control in AvalonDock. It arranges contained panes, handles fly out panes and floating windows.
So, following this example you could have implemented a custom region adapter for avalon, I worked with this implementation in a project getting awesome results.
Regards