Greetings,
I'm doing some experimentation trying to get Catel MVVM framework to work with Raddocking. I'm inheriting from DockingPanesFactory and modifying the RadPane CreatePaneForItem method as shown below:
01.
protected
override
RadPane CreatePaneForItem(
object
item)
02.
{
03.
Argument.IsNotNull(
"item"
, item);
04.
05.
var viewModel = item
as
DockViewModelBase;
06.
Type viewModelType = viewModel.GetType();
07.
08.
GetImpelementedInterface(viewModelType);
09.
10.
Log.Debug(
"'{0}' is not null. Constructing pane"
, (
object
)viewModel.GetType().Name);
11.
12.
var pane = (_isImplentingIToolPane) ?
new
RadPane() :
new
RadDocumentPane();
13.
14.
Log.Debug(
"Constructing instance of view for the pane using injection of data context"
);
15.
16.
Type view = _viewLocator.ResolveView(viewModelType);
17.
18.
pane.DataContext = viewModel;
19.
pane.Content = view;
20.
pane.Header = viewModel.Header;
21.
//pane.IsActive = viewModel.IsActive;
22.
//pane.CanFloat = viewModel.CanFloat;
23.
24.
Log.Debug(
"Pane: {0} - DataContext: {1} - View: {2}"
, pane.Header,pane.DataContext.GetType().Name,view.Name);
25.
26.
return
pane;
27.
//return base.CreatePaneForItem(item);
28.
}
When I check on pane.Content and pane.DataContext I could see that the view model is contained in the DataContext and the view is contained in the Content property as well. However, it's looking like there is an error in binding as I get this message:
1.
System.Windows.Data Warning: 4 : Cannot find source
for
binding with reference
'ElementName=DropDownButtonElement'
. BindingExpression:Path=IsChecked; DataItem=
null
; target element
is
'DropDownMenu'
(Name=
'DropDownMenuElement'
); target property
is
'IsOpen'
(type
'Boolean'
)
The pane displays fine but without any content. I only get name space of the view inside the pane.
11 Answers, 1 is accepted
After further reading I've realized that this isn't a binding issue. I have partially resolved this for now and got it working. In a way still not exactly sure why the first approach of setting the pane.Content to view type didn't work.
protected
override
RadPane CreatePaneForItem(
object
item)
{
Argument.IsNotNull(() => item);
var viewModel = item
as
DockViewModelBase;
if
(viewModel !=
null
)
{
Type viewModelType = viewModel.GetType();
_isImplentingIToolPane = GetImpelementedInterface(viewModelType);
Log.Debug(
"'{0}' is not null. Constructing pane"
, (
object
)viewModel.GetType().Name);
var pane = (_isImplentingIToolPane) ?
new
RadPane() :
new
RadDocumentPane();
Log.Debug(
"Constructing instance of view for the pane"
);
object
view = CreateViewContent(viewModel, viewModelType);
pane.DataContext = ((IView)view).DataContext;
pane.Content = view;
pane.Header = viewModel.Header;
pane.IsActive = viewModel.IsActive;
pane.CanFloat = viewModel.CanFloat;
pane.IsPinned = viewModel.IsPinned;
Log.Debug(
"Pane: {0} - DataContext: {1}"
, pane.Header, pane.DataContext.GetType().Name);
return
pane;
}
Log.Debug(
"Failed to create pane."
);
return
null
;
}
private
object
CreateViewContent(
object
viewModel, Type viewModelType)
{
Type viewType = _viewLocator.ResolveView(viewModelType);
FrameworkElement view = ViewHelper.ConstructViewWithViewModel(viewType, viewModel);
_viewManager.RegisterView((IView)view);
return
view;
}
Hello haytam,
I started looking at catel and was looking for a feedback with rad docking usage... Are you satisfied , do you see any particular clue?
thanks
I'm currently working on a shell of Raddocking and Catel. So far it looks like it is working but I have not tested it extensively and I'm still trying to make the code a little cleaner i.e. I'm throwing an InvalidOperationException. Anyway, I will paste whatever I have for you to see. So far, I see no issues having Catel work with Raddocking. As a matter of fact I am convinced now that Catel is superior to other MVVM frameworks (maybe not ReactiveUI :) )
public
class
CustomDockingPanesFactory : DockingPanesFactory
{
private
static
readonly
ILog Log = LogManager.GetCurrentClassLogger();
private
readonly
IViewLocator _viewLocator = ServiceLocator.Default.ResolveType<IViewLocator>();
private
readonly
IViewManager _viewManager = ServiceLocator.Default.ResolveType<IViewManager>();
private
bool
_isImplentingIToolPane;
protected
override
RadPane CreatePaneForItem(
object
item)
{
Argument.IsNotNull(() => item);
var viewModel = item
as
DockViewModelBase;
if
(viewModel !=
null
)
{
Type viewModelType = viewModel.GetType();
_isImplentingIToolPane = GetImplementedInterface(viewModelType);
Log.Debug(
"'{0}' is not null. Constructing pane"
, (
object
)viewModel.GetType().Name);
var pane = (_isImplentingIToolPane) ?
new
RadPane() :
new
RadDocumentPane();
Log.Debug(
"Constructing instance of view for the pane"
);
object
view = CreateViewContent(viewModel, viewModelType);
pane.DataContext = ((IView)view).DataContext;
pane.Content = view;
pane.Header = viewModel.Header;
pane.IsActive = viewModel.IsActive;
pane.CanFloat = viewModel.CanFloat;
pane.IsPinned = viewModel.IsPinned;
Log.Debug(
"Pane: {0} - DataContext: {1}"
, pane.Header, pane.DataContext.GetType().Name);
return
pane;
}
Log.Debug(
"Failed to create pane."
);
return
null
;
}
private
object
CreateViewContent(
object
viewModel, Type viewModelType)
{
Type viewType = _viewLocator.ResolveView(viewModelType);
FrameworkElement view = ViewHelper.ConstructViewWithViewModel(viewType, viewModel);
_viewManager.RegisterView((IView)view);
return
view;
}
private
bool
GetImplementedInterface(Type viewModelType)
{
Argument.IsNotNull(() => viewModelType);
IEnumerable<Type> typeInterfaces = viewModelType.GetInterfaces();
foreach
(Type type
in
typeInterfaces)
// My viewmodels inherit ViewModelBase and also implement interfaces for either the document or the tool pane
{
if
(type.Name ==
"IToolPane"
)
{
Log.Debug(
"{0} implements IToolPane"
, viewModelType.Name);
return
true
;
}
if
(type.Name ==
"IDocumentPane"
)
{
Log.Debug(
"{0} implements IDocumentPane"
, viewModelType.Name);
return
false
;
}
}
Log.ErrorAndThrowException<InvalidOperationException>(
"{0} does not implement IToolPane or IDocumentPane"
, viewModelType.Name);
return
false
;
}
protected
override
void
AddPane(RadDocking radDocking, RadPane pane)
{
RadPaneGroup group =
null
;
var paneModel = pane.DataContext
as
DockViewModelBase;
if
(paneModel !=
null
&& _isImplentingIToolPane)
{
switch
(paneModel.SavedDockState)
{
case
DockState.DockedRight:
group = radDocking.SplitItems.ToList().FirstOrDefault(i => i.Control.Name ==
"RightGroup"
)
as
RadPaneGroup;
if
(group !=
null
)
{
group.Items.Add(pane);
}
return
;
case
DockState.DockedBottom:
group = radDocking.SplitItems.ToList().FirstOrDefault(i => i.Control.Name ==
"BottomGroup"
)
as
RadPaneGroup;
if
(group !=
null
)
{
group.Items.Add(pane);
}
return
;
case
DockState.DockedLeft:
group = radDocking.SplitItems.ToList().FirstOrDefault(i => i.Control.Name ==
"LeftGroup"
)
as
RadPaneGroup;
if
(group !=
null
)
{
group.Items.Add(pane);
}
return
;
case
DockState.FloatingDockable:
RadSplitContainer splitContainer = radDocking.GeneratedItemsFactory.CreateSplitContainer();
group = radDocking.GeneratedItemsFactory.CreatePaneGroup();
splitContainer.Items.Add(group);
group.Items.Add(pane);
radDocking.Items.Add(splitContainer);
pane.MakeFloatingDockable();
return
;
case
DockState.FloatingOnly:
RadSplitContainer foSplitContainer = radDocking.GeneratedItemsFactory.CreateSplitContainer();
group = radDocking.GeneratedItemsFactory.CreatePaneGroup();
foSplitContainer.Items.Add(group);
group.Items.Add(pane);
radDocking.Items.Add(foSplitContainer);
pane.MakeFloatingOnly();
return
;
case
DockState.DockedTop:
default
:
return
;
}
}
if
(paneModel !=
null
&& !_isImplentingIToolPane)
{
group = radDocking.SplitItems.ToList().FirstOrDefault(i => i.Control.Name ==
"DocumentGroup"
)
as
RadPaneGroup;
if
(group !=
null
)
{
group.Items.Add(pane);
}
}
}
/// <summary>
/// Removes the <paramref name="pane" /> from the <see cref="T:Telerik.Windows.Controls.RadDocking" /> layout. By default clears the Header, Content, DataContext and call RemoveFromParent method.
/// </summary>
/// <param name="pane">The <see cref="T:Telerik.Windows.Controls.RadPane" /> to remove.</param>
protected
override
void
RemovePane(RadPane pane)
{
pane.DataContext =
null
;
pane.Content =
null
;
pane.RemoveFromParent();
}
}
}
I'm currently using Raddocking with Caliburn Micro but I think it's slow... I want to give a try to Catel... have to plan migration efforts... what about usage with other Telerik Controls?
Excuse me again,
Is it fast to add a rad pane or are you facing lag/UI freeze?
thanks
Haytham,
can you please share how do you add a Viewmodel to the CustomDockingPanesFactory?
have you got a repo on github ,something else?
Hello,
No I don't have it on GitHub since like I mentioned above it is still work in progress and the code is no where near presentable.
The view model can be resolved using Catel's service locator and then passed on through the observable item.
/// <summary>
/// Private method called to create a view model.
/// </summary>
/// <param name="viewModel"></param>
/// <returns></returns>
private
IViewModel ResolveViewModel(
string
viewModel)
{
Argument.IsNotNull(()=>viewModel);
Log.Debug(
"Resolving the main view model '{0}'"
, viewModel);
Type type = TypeCache.GetType(viewModel);
if
(type ==
null
)
{
Log.ErrorAndThrowException<NullReferenceException>(
"Could not retrieve type for 'moduleMainViewModelTypeName'"
);
}
return
(IViewModel)_serviceLocator.ResolveType(type);
}
/// <summary>
/// Private method called to create a view model.
/// </summary>
/// <param name="viewModelType"></param>
/// <returns></returns>
private
IViewModel ResolveViewModel(Type viewModelType)
{
Argument.IsNotNull(()=>viewModelType);
Log.Debug(
"Resolving the main view model - '{0}'"
, viewModelType.Name);
return
(IViewModel)_serviceLocator.ResolveType(viewModelType);
}
The view model for docking looks like this:
public
class
DockViewModelBase : ViewModelBase, IDockViewModelBase
{
private
IDefaultPaneSettings _defaultPaneSettings;
public
IPaneSettingsService PaneSettingsService {
get
;
set
;}
public
DockViewModelBase()
{
}
public
DockViewModelBase(IPaneSettingsService paneSettingsService)
{
this
.PaneSettingsService = paneSettingsService;
}
public
virtual
void
RetrieveDefaultPaneSettings(Type defaultSettingsType)
{
Argument.IsNotNull(() => defaultSettingsType);
_defaultPaneSettings = PaneSettingsService.GetDefaultConfiguration(defaultSettingsType);
this
.Header = _defaultPaneSettings.Header;
this
.DefaultDockState = _defaultPaneSettings.SavedDockState;
this
.SavedDockState = _defaultPaneSettings.SavedDockState;
this
.CurrentDockState = _defaultPaneSettings.SavedDockState;
this
.PreviousDockState = _defaultPaneSettings.SavedDockState;
this
.IsActive =
true
;
this
.IsHidden =
false
;
this
.CanFloat = _defaultPaneSettings.CanFloat;
this
.IsFloating = _defaultPaneSettings.IsFloating;
this
.IsPinned = _defaultPaneSettings.IsPinned;
}
public
virtual
string
Header {
get
;
set
; }
/// <summary>
/// This is set by the originator of the module for the view model
/// </summary>
public
DockState DefaultDockState {
get
;
set
; }
/// <summary>
/// This is the value that will be extracted from a file. This will be saved to a file if the user changes the dock state for the pane
/// </summary>
public
DockState SavedDockState {
get
;
set
; }
/// <summary>
/// Current dock state for the pane
/// </summary>
public
DockState CurrentDockState {
get
;
set
; }
/// <summary>
/// The last dock state for the pane before changing it to current
/// </summary>
public
DockState PreviousDockState {
get
;
set
; }
public
bool
IsActive {
get
;
set
; }
public
bool
IsHidden {
get
;
set
; }
public
bool
CanFloat {
get
;
set
; }
public
bool
IsFloating {
get
;
set
; }
public
bool
IsPinned {
get
;
set
; }
/// <summary>
/// A flag indicating whether current dock state is the same as the default dock state
/// </summary>
public
bool
IsDefaultDockState {
get
;
set
; }
public
Type ViewType;
public
event
EventHandler<EventArgs> Activated;
public
event
EventHandler<EventArgs> Deactivated;
}
I have created a class that retrieves the settings of the pane. Also, and in order to determine where the view model would be hosted on the Dock they have to implements either a IToolPane interface or an IDocumentPane interface
public
interface
IDocumentPane : IDockViewModelBase
{
}
public
interface
IToolPane : IDockViewModelBase
{
}
And now the view model for the docking panes.
public
class
DockingPanesViewModel : ViewModelBase
{
private
static
readonly
ILog Log = LogManager.GetCurrentClassLogger();
private
readonly
IDockingPanesService _dockingPanesService;
private
readonly
ICollection<IViewModel> _whenAvailableViewModels;
public
DockingPanesViewModel(IDockingPanesService dockingPanesService)
{
Argument.IsNotNull(()=> dockingPanesService);
this
.Panes =
new
ObservableItemCollection<DockViewModelBase>();
this
._dockingPanesService = dockingPanesService;
_whenAvailableViewModels =
new
List<IViewModel>();
this
._whenAvailableViewModels = LoadAvailableViewModels();
ShowPanesAtStart(_whenAvailableViewModels);
}
private
void
ShowPanesAtStart(ICollection<IViewModel> whenAvailableViewModels)
{
Argument.IsNotNull(() => whenAvailableViewModels);
foreach
(var viewModel1
in
whenAvailableViewModels)
{
var viewModel = (DockViewModelBase) viewModel1;
Panes.Add(viewModel);
}
}
private
ICollection<IViewModel> LoadAvailableViewModels()
{
return
_dockingPanesService.CreateWhenAvailableViewModels();
}
public
ObservableCollection<DockViewModelBase> Panes
{
get
{
return
GetValue<ObservableCollection<DockViewModelBase>>(PanesProperty); }
set
{ SetValue(PanesProperty, value); }
}
public
static
readonly
PropertyData PanesProperty = RegisterProperty(
"Panes"
,
typeof
(ObservableCollection<DockViewModelBase>),
null
);
}
I'm using a modified ModuleInfo and ModuleCatalog from the Prism library to pass on the view models at start up. The above code should help you get started.
Note that all this code does is test loading view models into the panes. I have not optimized it for asynchronous work yet.
If this isn't helpful I could upload the whole project to OneDrive.
Like I said this is work in progress.
Hello if you can share the code it would be great , have you also got an email I can write you on?
Thanks
Haytham can you please share it somewhere?
Thanks