Add a New ViewModel to ViewModelLocator in MVVM Light Toolkit - c#

I know this question is somewhat basic, but at the current moment, I am completely lost as to how I should add a new ViewModel to my ViewModelLocator class in MVVM Light Toolkit.
My current implementation looks like so:
First assume that I have a Window named Settings, a ViewModel named SettingsViewModel and a ViewModelLocator ViewModelLocator.
First I call CreateSettings() in the VieModelLocator constructor:
public ViewModelLocator()
{
if (ViewModelBase.IsInDesignModeStatic)
{
}
else
{
CreateSettings();
}
CreateMain();
}
Note that this will always run as I'm not using blend and build the application each time I try to run it. Now for the `CreateSettings() method.
I had no idea what I was doing so I tried to play it safe and model everything after the methods used for creating and managing the MainViewModel.
public static void CreateSettings()
{
if (_settings == null)
{
_settings = new SettingsViewModel();
}
}
Then another few methods modeled after those used for the MainViewModel:
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public SettingsViewModel Settings
{
get
{
return SettingsStatic;
}
}
public static SettingsViewModel SettingsStatic
{
get
{
if (_settings == null)
{
CreateSettings();
}
return _settings;
}
}
And in my Settings Window Xaml:
<Window x:Class="_5500A_Auto_Calibrator.Settings"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Settings" Height="300" Width="300"
DataContext="{Binding Source={StaticResource Locator}, Path=Settings}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
The window is then opened from my MainViewModel like so:
Settings settings = new Settings();
settings.Show();
If I try this, I receive an exception:
"'Provide value on 'System.Windows.StaticResourceExtension' threw an exception.' Line number '4' and line position '39'."
And an inner exception of:
"Cannot find resource named 'Locator'. Resource names are case sensitive."
I've read up on errors involving a Window's inability to find the Locator resource, but most have to do with blend.
My current take is that I'm doing something wrong, but there's so little documentation as to adding new ViewModels that I'm unsure what I'm doing wrong.
Edit:
My App.Xaml:
<Application x:Class="_5500A_Auto_Calibrator.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:_5500A_Auto_Calibrator.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
StartupUri="MainWindow.xaml"
mc:Ignorable="d">
<Application.Resources>
<!--Global View Model Locator-->
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="True" />
</Application.Resources>
</Application>

This is how it usually looks:
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IDataService, Design.DesignDataService>();
}
else
{
SimpleIoc.Default.Register<IDataService, DataService>();
}
SimpleIoc.Default.Register<MainViewModel>();
}
/// <summary>
/// Gets the Main property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
...
}
the ViewModelLocator is static, yours doesn't seem to be. It usually sits in the ViewModel folder (assuming you installed mvvmlight with the nuget and then added a new wvvm project.
it then proceeds to have the 2 cases for design and for runtime. (if you don't use it, you can skip the if (IsInDesignMode) ... bit, and just put your logic. (though it's a shame, since it's nice to have a preview of some fake data in VS designer ...)
Adding new ViewModels usually involves creating a property of that type, and registering them with the locator, so you can then retrieve them ... but this differs and can be done differently I believe ...
Hope this help, and if there's anything else I can help with, do let me know.

Your data context binding tries to apply earlier, than resource is declared. Try to declare binding this way (of course, this should help only if either MainSkin.xaml or application resources contain Locator resource):
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Window.DataContext>
<Binding Source="{StaticResource Locator}" Path="Settings"/>
</Window.DataContext>

Related

Different ways of using ResourceDictionary.MergedDictionaries

I was going through some code in our product and saw some colleagues using ResourceDictionary.MergedDictionaries in a way I had not seen it used before:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<toolTips:ToolTips />
<styles:ControlStyles />
<icons:IconDictionary />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
tooltips:ToolTips and all the other elements in the MergedDictionaries are ResourceDictionaries.
The regular way of using these according to the internet is to use <ResourceDictionary Source="uri to your xaml file"/>.
So is there any practical difference between both?
If this way works why isn't it used more often as it plays well with code completion?
I've used ResourceDicionary this way only once on a big project and it was benefical in my situation.
Suppose that you have ResourceDictionary in MyDictionary.xaml file.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="YourNamespace.MyDictionary">
</ResourceDictionary>
You can add an x:Class attribute to the ResourceDictionary element and specify the fully qualified name of the code-behind class.
Let's create MyDictionary.xaml.cs with class MyDictionary (name can be different from the name of the xaml file).
public partial class MyDictionary
{
public MyDictionary()
{
InitializeComponent();
}
}
A class must be a partial class. The constructor must be added to the class and InitializeComponent method must be called. The InitializeComponent method will be automatically generated for the class if you set the x:Class attribute in MyDictionary.xaml
Now you can reference MyDictionary in MergedDictionaries
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<local:MyDictionary/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
If you add some DataTemplate into MyDictionary.xaml you can create event handlers in code-behind (handlers will be automatically generated by VS)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="YourNamespace.MyDictionary">
<DataTemplate x:Key="MyTemplate">
<Button Click="Button_Click"/>
</DataTemplate>
</ResourceDictionary>
Code-behind:
public partial class MyDictionary
{
public MyDictionary()
{
InitializeComponent();
}
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
// custom logic
// edit another element, etc.
}
}
If the class is inherited from the ResourceDictionary class then other resources can be accessed from the code-behind.
Example of usage of data template defined in MyDictonary:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<local:MyDictionary/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<ContentControl ContentTemplate="{StaticResource MyTemplate}"/>
</Grid>
From my point of view the biggest advantages are that you can encapsulate logic into separated files (it's easy to maintain and add new features in big projects) and avoid referencing ResourceDictionaries by <ResourceDictionary Source="uri to your xaml file"/>.

Adding resources to an instance of WPF window

I'm making an AutoCAD .net program that has a WPF window as the interface. Currently the WPF interface is being referenced into the AutoCAD .net aplication and I'm calling the window from AutoCAD as follows.
public class Class1
{
public static WPFWindow.MainWindow mainWindow = new WPFWindow.MainWindow();
[CommandMethod("Launch", CommandFlags.Session)]
public void Launch()
{
Autodesk.AutoCAD.ApplicationServices.Application.ShowModalWindow(mainWindow);
}
}
This works fine until I start adding any form of resource to the WPF window I'm adding in. eg The following works until
<Window x:Class="WPFWindow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFWindow"
mc:Ignorable="d"
Title="Test" Height="450" Width="800"
WindowStyle="None"
AllowsTransparency="True"
>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes/Styles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Button Content="Press Me"/>
</Grid>
....I reference a static resource style for the window
WindowStyle="None"
AllowsTransparency="True"
Style="{StaticResource MainWindow}"
>
With the static resource when I run the "Launch" command in AutoCAD the program fails to find the static resource. I'm unsure how to get the instance of the WPFWindow to find the resource using C# code. As a test I added the WPFWindow as a reference to a WPF application and managed to get it to find the resource using the Pack URI
<ResourceDictionary Source="pack://application:,,,/WPFWindow;component/Themes/Styles.xaml"/>
Is there a C# equivalent of that I can use for the instance of the WPFWindow.MainWindow?
I managed to get it to work by adding the resources to the window I was referencing in its code behind files.
namespace WPFWindow
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
this.Resources.Source = new Uri(#"pack://application:,,,/WPFWindow;component/Themes/Styles.xaml"", UriKind.Absolute);
InitializeComponent();
}
}
}
I think this allowed the static resources to be loaded in before they were called for the window.
You cant use Staticresource in the root tag for external Resourcedictionaries. At time of Initialization the resource is not present. Linking it in ctor before calling InitializeComponent (as you did) does actually the same...
...
WindowStyle="None"
AllowsTransparency="True"
Style="{DynamicResource MainWindow}"
...
will work.

Implementing own ViewModelLocator

I wanted to implement ViewModelLocator by my own. So I implemented the simplest app in the world. I did everything as in this tutorial. But I'm still getting an exception:
XamlParseException occured
Exception thrown: 'System.Windows.Markup.XamlParseException' in
PresentationFramework.dll
Additional information: 'Provide value on
'System.Windows.StaticResourceExtension' threw an exception.' Line
number '8' and line position '9'.
This is this line:
DataContext="{Binding MainWindowViewModel, Source={StaticResource ViewModelLocator}}">
Here is the code:
App.xaml
<Application x:Class="ViewModelLocatorDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModelLocatorDemo="clr-namespace:ViewModelLocatorDemo">
<Application.Resources>
<viewModelLocatorDemo:ViewModelLocator x:Key="ViewModelLocator"/>
</Application.Resources>
</Application>
App.xaml.cs
namespace ViewModelLocatorDemo
{
using System.Windows;
using ViewModelLocatorDemo.Views;
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow mainWindow = new MainWindow();
mainWindow.Show();
}
}
}
ViewModelLocator.cs
namespace ViewModelLocatorDemo
{
using ViewModels;
public class ViewModelLocator
{
public MainWindowViewModel MainWindowViewModel
{
get { return new MainWindowViewModel(); }
}
}
}
MainWindow.xaml
<Window x:Class="ViewModelLocatorDemo.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300"
DataContext="{Binding MainWindowViewModel, Source={StaticResource ViewModelLocator}}">
<Grid>
<Frame x:Name="MainFrame" Margin="50" BorderThickness="2" BorderBrush="Black" />
</Grid>
</Window>
MainWindowViewModel.cs
namespace ViewModelLocatorDemo.ViewModels
{
public class MainWindowViewModel
{
public string MainText { get; set; }
public MainWindowViewModel()
{
MainText = "The first page";
}
}
}
In this answer I found:
Make sure that the resources are defined before the usage (in Xaml
parsing order). The easiest way is to place it into App.xaml
So I have it in App.xaml. If somebody would me explain what's going on here? Why am I getting this error?
You are running into this bug WPF - App.xaml file does not get parsed if my app does not set a StartupUri?
From that page:
There's a VS code generation bug where the code necessary to connect to the rest of the program sometimes is not inserted when contains only one entry and does not have a StartupUri attribute.
From that page, there are 3 solutions (summarizing here for completeness):
Add x:Name="App"
Add more resources in App.xaml like <viewModelLocatorDemo:ViewModelLocator x:Key="ViewModelLocator"/> and <viewModelLocatorDemo:ViewModelLocator x:Key="ViewModelLocator2"/>
Rather than overriding OnStartup, try using an event instead, Startup="Application_Startup"
This was definitely not obvious, and was difficult to troubleshoot and even find an answer for in my own search. Hopefully this answer will help others find the other answer.

Cannot access an XAML class-instance from code

I've created an class which will be the DataContext for my app, and instantiated it via XAML:
<Window x:Class="MyApp.UI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myDataModel="clr-namespace:MyApp.MyDataModel"
Title="MainWindow">
<Window.Resources>
<myDataModel:MyDataClass x:Name="the_DataModel" x:Key="a_DataModel"/>
</Window.Resources>
I want to act on this object in the constructor of my Window:
public MainWindow()
{
InitializeComponent();
the_DataModel.LoadFromFile(); // One of these *should* work!
a_DataModel.LoadFromFile();
}
However it seems that neither name (the_DataModel, nor a_DataModel) is a member of the Window class. When I type this., and use auto-complete, I cannot find anything that resembles the object I created in XAML.
How can I create an instance of a class in XAML, and access it in code?
Since you have added it as a resource in window resources, you can get it from Resource collection by indexing with resource key.
MyDataClass dataModel = (MyDataClass)Resources["a_DataModel"];
dataModel.LoadFromFile();

Caliburn Micro and ModernUI Examples/Tutorials

does anyone have an example or tutorial on how to use Caliburn Micro together with ModernUi (https://mui.codeplex.com)?
Ok so I had a quick mess about with it and a look on the Mui forums and this seems to be the best approach:
Since the window loads content from URLs you need to take a view-first approach, and then locate the appropriate VM and bind the two.
The best way to do this appears to be via the ContentLoader class which is used to load the content into the ModernWindow when it is requested. You can just subclass DefaultContentLoader and provide the necessary CM magic to bind up loaded items:
public class ModernContentLoader : DefaultContentLoader
{
protected override object LoadContent(Uri uri)
{
var content = base.LoadContent(uri);
if (content == null)
return null;
// Locate the right viewmodel for this view
var vm = Caliburn.Micro.ViewModelLocator.LocateForView(content);
if (vm == null)
return content;
// Bind it up with CM magic
if (content is DependencyObject)
{
Caliburn.Micro.ViewModelBinder.Bind(vm, content as DependencyObject, null);
}
return content;
}
}
Your CM bootstrapper should just bootstrap a ModernWindow viewmodel which is backed by a ModernWindow based view (CM tries to use EnsureWindow which creates a new basic WPF Window class, unless of course your control already inherits from Window which ModernWindow does. If you need all dialogs and popups to be MUI you might need to reimplement WindowManager):
public class Bootstrapper : Bootstrapper<ModernWindowViewModel>
{
}
Which can be a conductor (OneActive) and looks like this:
public class ModernWindowViewModel : Conductor<IScreen>.Collection.OneActive
{
}
And XAML for the view is
ModernWindowView.xaml
<mui:ModernWindow x:Class="WpfApplication4.ViewModels.ModernWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mui="http://firstfloorsoftware.com/ModernUI"
Title="ModernWindowView" Height="300" Width="300" ContentLoader="{StaticResource ModernContentLoader}">
<mui:ModernWindow.MenuLinkGroups>
<mui:LinkGroupCollection>
<mui:LinkGroup GroupName="Hello" DisplayName="Hello">
<mui:LinkGroup.Links>
<mui:Link Source="/ViewModels/ChildView.xaml" DisplayName="Click me"></mui:Link>
</mui:LinkGroup.Links>
</mui:LinkGroup>
</mui:LinkGroupCollection>
</mui:ModernWindow.MenuLinkGroups>
</mui:ModernWindow>
Obviously you need to make the loader a resource too:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.xaml" />
<ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.Dark.xaml"/>
<ResourceDictionary>
<framework:ModernContentLoader x:Key="ModernContentLoader"></framework:ModernContentLoader>
<wpfApplication4:Bootstrapper x:Key="Bootstrapper"></wpfApplication4:Bootstrapper>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Here's the ChildViewModel I'm using as a test:
public class ChildViewModel : Conductor<IScreen>
{
public void ClickMe()
{
MessageBox.Show("Hello");
}
}
And the XAML for that (just a button)
<UserControl x:Class="WpfApplication4.ViewModels.ChildView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525">
<Grid>
<StackPanel>
<TextBlock >Hello World</TextBlock>
<Button x:Name="ClickMe" Width="140" Height="50">Hello World</Button>
</StackPanel>
</Grid>
</UserControl>
And the proof of concept:
I create a very, very simple sample of chat app using Modern UI for WPF, Caliburn Micro and MEF.
https://github.com/gblmarquez/mui-sample-chat
I hope it helps

Categories