How to "Navigate" with a Prism Custom adapter? - wpf

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

Related

ActionCollection of Blend custom Behavior is always empty

I have created Blend behavior, but when I add children to it in xaml, they does not appear in the collection. What might be the reason of that ?
When app is running, Actions collection does not contain any action, though it certainly should.
<Helpers:EnterKeyUpEventBehavior>
<Helpers:CloseFlyoutAction />
</Helpers:EnterKeyUpEventBehavior>
[ContentProperty(Name = "Actions")]
class EnterKeyUpEventBehavior : DependencyObject, IBehavior
{
public static readonly DependencyProperty ActionsProperty = DependencyProperty.Register(
"Actions", typeof (ActionCollection), typeof (EnterKeyUpEventBehavior), new PropertyMetadata(default(ActionCollection)));
public ActionCollection Actions
{
get
{
var actions = (ActionCollection) GetValue(ActionsProperty);
if (actions == null)
{
actions = new ActionCollection();
base.SetValue(ActionsProperty, actions);
}
return actions;
}
set { SetValue(ActionsProperty, value); }
}
private TextBox _associatedTextBox;
public DependencyObject AssociatedObject
{
get { return _associatedTextBox; }
}
public void Attach(DependencyObject associatedObject)
{
_associatedTextBox = associatedObject as TextBox;
if(_associatedTextBox == null)
throw new ArgumentException("This Behavior only works with TextBox control!");
_associatedTextBox.KeyUp += _associatedTextBox_KeyUp;
Actions = new ActionCollection();
}
void _associatedTextBox_KeyUp(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Enter)
{
Interaction.ExecuteActions(_associatedTextBox, Actions, null);
}
}
public void Detach()
{
_associatedTextBox.KeyUp -= _associatedTextBox_KeyUp;
}
}
public ActionCollection Actions
{
get { return (ActionCollection) GetValue(ActionsProperty); }
set { SetValue(ActionsProperty, value); }
}
public EnterKeyUpEventBehavior()
{
Actions = new ActionCollection();
}
The xaml parsing and instatiation mechanism does not use your getter and setter, it uses GetValue(ActionsProperty) and SetValue(ActionsProperty) directly, circumventing your "lazy init".
In my experience when I want to use a collection type property as ContentProperty I have to either use a plain List or a DependencyObjectCollection (this is needed when you want the DataContext inheritance mechanism to function properly).
either:
public List<Action> Actions
{
get { return (List<Action>) GetValue(ActionsProperty); }
set { SetValue(ActionsProperty, value); }
}
public EnterKeyUpEventBehavior()
{
Actions = new List<Action>();
}
or:
public DependencyObjectCollection Actions
{
get { return (DependencyObjectCollection) GetValue(ActionsProperty); }
set { SetValue(ActionsProperty, value); }
}
public EnterKeyUpEventBehavior()
{
Actions = new DependencyObjectCollection();
}

A WPF docking library supporting MVVM

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

ObservableCollection items not showing updates

I want a list of items that can display either an item's 'greek' or 'english' name, depending on a user toggling between the two. All of the items in the list implement INPC.
Since each item has a GreekName property and an RomanName property, the strategy I am using is to simply change the items DisplayName property. Unit tests and log output indicate that the DisplayName for each item does change and does fire INPC, but the list does not update.
The list is an ObservableCollection. I am wondering if this fails to update because the hash code doesn't change? Does that mean that the only way to replace the item in the list with a new one?
Some code below...
Cheers,
Berryl
public class MasterViewModel : ViewModelBase
{
public ObservableCollection<DetailVm> AllDetailVms
{
get { return _allDetailVms; }
}
private readonly ObservableCollection<DetailVm> _allDetailVms;
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
if (e.NewItems != null && e.NewItems.Count != 0)
foreach (DetailVm vm in e.NewItems) vm.PropertyChanged += OnGreekGodChanged;
if (e.OldItems != null && e.OldItems.Count != 0)
foreach (DetailVm vm in e.OldItems) vm.PropertyChanged -= OnGreekGodChanged;
}
private void OnGreekGodChanged(object sender, PropertyChangedEventArgs e)
{
var detailVm = (DetailVm)sender;
// if DisplayName has changed we want to refresh the view & its filter
var displayName = ExprHelper.GetPropertyName<DetailVm>(x => x.DisplayName);
if (e.PropertyName == displayName)
Log.Info("'{0} reports it's display name has changed", detailVm.DisplayName);
}
private void _flipGreekOrRomanDisplay(string newName, Func<DetailVm, string> property)
{
foreach (var detailVm in _allDetailVms)
{
Log.Info("To '{0}', before change: '{1}'", newName, detailVm.DisplayName);
detailVm.DisplayName = property(detailVm);
Log.Info("To '{0}', after change: '{1}'", newName, detailVm.DisplayName);
}
NameFilterLabelText = newName;
NotifyOfPropertyChange(() => NameFilterLabelText);
NotifyOfPropertyChange(() => UseGreekName);
NotifyOfPropertyChange(() => UseRomanName);
}
}
My idiocy - my databinding was off. The code was fine and the view updates by virtue of it's items firing INPC, as expected.

How to register custom module manager in PRISM?

I've created some simple custom ModuleManager in my silverlight application based on PRISM. I also registered this type in bootstrapper, but PRISM still use the default manager. The constructor of my CustomModuleManager is called, but the property ModuleTypeLoaders is never accessed. I can't figure it out, how can I make it work properly?
Here is bootstrapper.cs
protected override void ConfigureContainer()
{
Container.RegisterType<IShellProvider, Shell>();
Container.RegisterType<IModuleManager, CustomModuleManager>();
base.ConfigureContainer();
}
CustomModuleManager.cs
public class CustomModuleManager : ModuleManager
{
IEnumerable<IModuleTypeLoader> _typeLoaders;
public CustomModuleManager(IModuleInitializer moduleInitializer,
IModuleCatalog moduleCatalog,
ILoggerFacade loggerFacade)
: base(moduleInitializer, moduleCatalog, loggerFacade)
{
MessageBox.Show("ctor");
}
public override IEnumerable<IModuleTypeLoader> ModuleTypeLoaders
{
get
{
MessageBox.Show("getter");
if (_typeLoaders == null)
{
_typeLoaders = new List<IModuleTypeLoader>
{
new CustomXapModuleTypeLoader()
};
}
return _typeLoaders;
}
set
{
MessageBox.Show("setter");
_typeLoaders = value;
}
}
}
CustomXapModuleTypeLoader.cs
public class CustomXapModuleTypeLoader : XapModuleTypeLoader
{
protected override IFileDownloader CreateDownloader()
{
return new CustomFileDownloader();
}
}
CustomFileDownloader.cs
public class CustomFileDownloader : IFileDownloader
{
public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted;
readonly FileDownloader _dler = new FileDownloader();
public CustomFileDownloader()
{
_dler.DownloadCompleted += DlerDownloadCompleted;
}
void DlerDownloadCompleted(object sender, DownloadCompletedEventArgs e)
{
_dler.DownloadCompleted -= DlerDownloadCompleted;
if (DownloadCompleted != null)
{
if (e.Cancelled || e.Error != null)
{
DownloadCompleted(this, e);
}
else
{
DownloadCompleted(this,
new DownloadCompletedEventArgs(e.Result,
e.Error,
e.Cancelled,
e.UserState));
}
}
}
public void DownloadAsync(Uri uri, object userToken)
{
_dler.DownloadAsync(uri, userToken);
}
}
Reorder your call to base.ConfigureContainer so that yours wins (last one wins):
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType<IShellProvider, Shell>();
Container.RegisterType<IModuleManager, CustomModuleManager>();
}

How can I use a Panel as a Region in Prism?

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;
}
}

Resources