MVVM - Open Second View Windows 8 - c#

Edit: Example App -> http://www10.zippyshare.com/v/29730402/file.html
I'm programming an app for Windows 8 & Windows Phone. I'm using the portable class library (see this article http://blog.tattoocoder.com/2013/01/portable-mvvm-light-move-your-view.html).
My problem is: How can I open a second window by clicking on a button by using the MVVM-pattern? I don't want to do it in the behind-code.
My datacontext for the Windows 8 app looks in the xaml like this
DataContext="{Binding Main, Source={StaticResource Locator}}"
which uses the ViewModel of the PCL (= ViewModel for both, W8 & WP8)
xmlns:vm="using:Mvvm.PCL.ViewModel"
I don't know how to assign 2 datacontext to my MainPage.xaml, nor do I know how to assign my MainPage.xaml to the ViewModel for my Windows 8 app.
I've tried something like this:
Command="{Binding DisplayView}" CommandParameter="SecondView"
but the program uses the ViewModel for both platforms and I can't program there the windows-assignment for the specific platforms. (it should look something like this Opening multiple views by clicking button using MVVM silverlight approach ...)
To make it clear:
I have 2 projects.
Both MainWindows of the projects refer to the ViewModel of the "MainProject".
If I want to click on a button in my MainWindow, I want to open a new view, but I can only use the ViewModel for both projects, which means that I can't use any views of the 2 projects in my ViewModel of the "MainProject".
edit: I've seen that many people use ContentControl. (Still doesn't work. Btw im new to MVVM).
<ContentControl Grid.Row="2" Content="{Binding CurrentView}" IsTabStop="False" Margin="10" />
<Button Command="{Binding DisplayView}" CommandParameter="SecondView">
MainViewModel.cs (For both platforms)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using Mvvm.PCL.Model;
#if NETFX_CORE
using Mvvm.Store.Views;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#endif
namespace Mvvm.PCL.ViewModel
{
public class MainViewModel : ViewModelBase, INotifyPropertyChanged
{
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
#if NETFX_CORE
DisplayView = new RelayCommand<string>(DisplayViewCommandExecute);
#endif
}
#region Commands
public RelayCommand<string> DisplayView { get; private set; }
#endregion
#if NETFX_CORE
#region CurrentView Property
public const string CurrentViewPropertyName = "CurrentView";
private Page _currentView;
public Page CurrentView
{
get { return _currentView; }
set
{
if (_currentView == value)
return;
_currentView = value;
RaisePropertyChanged(CurrentViewPropertyName);
}
}
private SecondView _secondview = new SecondView();
public SecondView SecondView
{
get
{
return _secondview;
}
}
#endregion
private void DisplayViewCommandExecute(string viewName)
{
switch (viewName)
{
case "SecondView":
CurrentView = _secondview;
var frame = (Frame)Window.Current.Content;
frame.Navigate(typeof(SecondView));
break;
}
}
#endif
}
}

Related

LiveCharts2 with WPF & MVVM: How to bind a dynamically calculated property to a chart series?

Adhering to the MVVM pattern, how do I bind a chart to dynamically calculated property data and make the chart reflect changes?
I'm working on a GUI application where certain parameters are adjustable via controls and the function values of a computational model dependent on these parameters should be plotted in real time when any parameters are adjusted.
I'm programming in C# using WPF and want/need to use the MVVM pattern, which I have only recently learned (I'm also relatively new to C#). I chose LiveCharts2 for plotting because it can be used with the MVVM pattern; a different library that does the job (X-Y-diagrams that automatically update based on data binding) would be fine, too.
The following code is a simplification of my actual application, which is much more complex. The model is a simple parabola function y=a*x² where a is the parameter that is adjusted via a TextBox in the GUI. When adjusted, the parabola function should be updated in the diagram.
MainWindow.xaml:
<Window x:Class="TestLvc.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:TestLvc"
xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:MainViewModel}"
Title="MainWindow" Height="600" Width="800">
<Grid>
<StackPanel>
<Grid Name="DrawingGrid" Width="400" Height="400">
<lvc:CartesianChart Series="{Binding DataPointCalculator.SeriesBindable}" />
</Grid>
<TextBox Text="{Binding DataPointCalculator.ParabolaFactor}" Width="30" />
<Button>Lose Focus from TextBox in order to update binding</Button>
<Label Name="LabelBoundValue" Content="{Binding DataPointCalculator.ParabolaFactor}" />
<Label Content="^- Bound value" />
</StackPanel>
</Grid>
</Window>
Notes:
The button doesn't do anything on its own, it just makes the TextBox lose focus and thereby apply the input value.
The Label below the button has the same binding as the TextBox in order to validate that the binding is working.
MainWindow.xaml.cs:
// Choose one:
#define use_dependent_property // Preferred solution: Doesn't work.
#define recalculate_points_in_setter // Works.
#define recalculate_property_in_setter // Should be similar to recalculate_points_in_setter and therefore working, but doesn't.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using LiveChartsCore;
using LiveChartsCore.Defaults;
using LiveChartsCore.SkiaSharpView;
namespace TestLvc;
public partial class MainWindow : Window
{
public MainViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new MainViewModel();
DataContext = vm;
}
}
public class MainViewModel
{
public MainViewModel()
{
DataPointCalculator = new ParabolaCalculator(5);
}
public ParabolaCalculator DataPointCalculator { get; set; }
}
public class ParabolaCalculator : INotifyPropertyChanged
{
private static readonly double[] XValues =
Enumerable.Range(-20, 41).Select(stepIndex => (double) stepIndex * 1).ToArray();
private double _parabolaFactor;
public ParabolaCalculator(double parabolaFactor = 1)
{
ParabolaFactor = parabolaFactor;
#if use_dependent_property
LineSeriesSeries.Values = DataPointsDependent;
#elif recalculate_points_in_setter
LineSeriesSeries.Values = DataPointsCalculated;
#elif recalculate_property_in_setter
LineSeriesSeries.Values = DataPointsCalculated;
#endif
SeriesBindable = new List<ISeries> {LineSeriesSeries};
}
public ObservableCollection<ObservablePoint> DataPointsCalculated { get; set; }
public ObservableCollection<ObservablePoint> DataPointsDependent => CalculateDataPoints();
public List<ISeries> SeriesBindable { get; set; }
public LineSeries<ObservablePoint> LineSeriesSeries { get; set; } = new();
public double ParabolaFactor
{
get => _parabolaFactor;
set
{
_parabolaFactor = value;
OnPropertyChanged(nameof(ParabolaFactor));
#if use_dependent_property
// Don't do calculations manually, because the data points are dynamically calculated -- but how is the chart informed that the underlying parameter has changed?
// Raising property-changed events doesn't help.
OnPropertyChanged(nameof(SeriesBindable));
OnPropertyChanged(nameof(DataPointsDependent));
OnPropertyChanged(nameof(LineSeriesSeries));
#elif recalculate_points_in_setter
RecalculateDataPoints();
#elif recalculate_property_in_setter
DataPointsCalculated = CalculateDataPoints();
#endif
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void RecalculateDataPoints()
{
DataPointsCalculated ??= CalculateDataPoints();
foreach (var point in DataPointsCalculated) point.Y = ParabolaFunction((double) point.X);
}
public ObservableCollection<ObservablePoint> CalculateDataPoints()
{
ObservableCollection<ObservablePoint> points = new();
foreach (var x in XValues) points.Add(new ObservablePoint(x, ParabolaFunction(x)));
return points;
}
private double ParabolaFunction(double x)
{
return ParabolaFactor * (x * x);
}
}
I included three compiler constants that lead to different implementations:
use_dependent_property would be the way I want it to work: The model data depends on the parameter value, so it makes sense to implement the model data as the dependent property DataPointsDependent. The chart data can be bound to this property, but the chart is not updated when the parameter is changed. How can this be achieved?
recalculate_points_in_setter: Here, the chart is bound to the property DataPointsCalculated which is a ObservableCollection<ObservablePoint> and whose ObservablePoints are updated in the parameter's setter. This works and would be OK for a simple case like this, but considering a larger number of parameters and a more complex view model, it does not seem like an option to me. It also looks to me like this solution works around the MVVM pattern, because with MVVM and data binding, you should not have to deal with manual updates, should you?
recalculate_property_in_setter: Bonus question. The chart is again bound to ObservableCollection<ObservablePoint> DataPointsCalculated and the model data are updated in the parameter's setter, but here the whole DataPointsCalculated is replaced instead of it's ObservablePoints. Given that the second approach works, why won't this one work? Based on the documentation on automatic updating, I think it should, because:
// since valuesCollection is of type ObservableCollection
// LiveCharts will update when you add, remove, replace or clear the collection
var valuesCollection = new ObservableCollection();
var lineSeries = new LineSeries();
lineSeries.Values = valuesCollection;
Note on LiveCharts2: This is still a pre-release, but I tried the same with LiveCharts0 with the same outcome.

Xamarin: Binding property not found

This app works just fine in UWP. I have ripped out everything except one of the more basic properties that is failing on Android. It looks like this:
MyPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ViewModels="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.Views.MyApp">
<ContentPage.BindingContext>
<ViewModels:MyViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<ScrollView>
<StackLayout Style="{StaticResource PageForm}">
<Picker ItemsSource="{Binding Modes}"
ItemDisplayBinding="{Binding Value}"
SelectedItem="{Binding SelectedMode}" />
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
MyPage.cs
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace MyApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyApp : ContentPage
{
public MyApp ()
{
InitializeComponent ();
}
}
}
MyViewModel.cs
using MyApp.Models;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace MyApp.ViewModels
{
public class MyViewModel: INotifyPropertyChanged
{
List<Mode> _modes;
Mode _selectedMode;
public event PropertyChangedEventHandler PropertyChanged;
public MyViewModel()
{
Modes = new List<Mode>()
{
new Mode() { Key=ModeType.Mode1, Value="Mode1" },
new Mode() { Key=ModeType.Mode2, Value="Mode2" }
};
SelectedMode = Modes.Single(m => m.Key == ModeType.Mode1);
}
public List<Mode> Modes {
get { return _modes; }
set {
_modes = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Modes"));
}
}
public Mode SelectedMode {
get {
return _selectedMode;
}
set {
_selectedMode = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SelectedMode"));
}
}
}
}
Mode.cs
namespace MyApp.Models
{
public enum ModeType { Mode1, Mode2 };
public class Mode
{
public ModeType _key;
public string _value;
public ModeType Key {
get
{
return _key;
}
set
{
_key = value;
}
}
public string Value {
get
{
return _value;
}
set
{
_value = value;
}
}
}
}
and what I see in the Debug console is
[0:] Binding: 'Value' property not found on 'MyApp.Models.Mode', target property: 'Xamarin.Forms.Picker.Display'
[0:] Binding: 'Value' property not found on 'MyApp.Models.Mode', target property: 'Xamarin.Forms.Picker.Display'
[0:] Binding: 'SelectedMode' property not found on 'MyApp.ViewModels.'MyApp', target property: 'Xamarin.Forms.Picker.SelectedItem'
Like I said this works if I run it as a UWP app but when I try it on Android it just doesn't work. That's about all I can say since it doesn't really say what the problem is other than the errors above which don't make sense.
The rest of the view model actually works. The main part of the app works, I can even run the code on this view model. If I create a simple string binding that will work, even on Android.
Any help is appreciated.
The answer is total magic to me. If someone can please explain this I will mark your answer as the accepted one.
Anroid Project File > Properties > Linking > Set to None.
It still didn't work so I closed Visual Studio and deleted the bin and obj directories in the PCL and Android projects. Finally it worked.
One other thing is this seems like I've now lost the ability to have linking be set to sdk and user assemblies. What if I need that at some point?
Use a one way binding to avoid having these binding errors in the debug console.
Text="{Binding [Name], Source={x:Static i18n:Translator.Instance}, Mode=OneWay}"
If you need TwoWay binding, make sure the bound model objects implement INotifyPropertyChanged as Markus Michel indicated.
Your mode model class also needs to implement INotifyPropertyChanged

Sharing data between different ViewModels

I'm trying to develop an easy MVVM project that it has two windows:
The first window is a text editor, where I bind some properties such as FontSize or BackgroundColor:
<TextBlock FontSize="{Binding EditorFontSize}"></TextBlock>
its DataContext is MainWindowViewModel:
public class MainWindowViewModel : BindableBase
{
public int EditorFontSize
{
get { return _editorFontSize; }
set { SetProperty(ref _editorFontSize, value); }
}
.....
The second window is the option window, where I have an slider for changing the font size:
<Slider Maximum="30" Minimum="10" Value="{Binding EditorFontSize }" ></Slider>
its DataContext is OptionViewModel:
public class OptionViewModel: BindableBase
{
public int EditorFontSize
{
get { return _editorFontSize; }
set { SetProperty(ref _editorFontSize, value); }
}
.....
My problem is that I have to get the value of the slider in the option window and then I have to modify the FontSize property of my TextBlock with this value. But I don't know how to send the font size from OptionViewModel to MainViewModel.
I think that I should use:
A shared model
A model in MainWindowViewModel and a ref of this model in OptionViewModel
Other systems like notifications, messages ...
I hope that you can help me. It's my first MVVM project and English isn't my main language :S
Thanks
Another option is to store such "shared" variables in a SessionContext-class of some kind:
public interface ISessionContext: INotifyPropertyChanged
{
int EditorFontSize { get;set; }
}
Then, inject this into your viewmodels (you are using Dependency Injection, right?) and register to the PropertyChanged event:
public class MainWindowViewModel
{
public MainWindowViewModel(ISessionContext sessionContext)
{
sessionContext.PropertyChanged += OnSessionContextPropertyChanged;
}
private void OnSessionContextPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "EditorFontSize")
{
this.EditorFontSize = sessionContext.EditorFontSize;
}
}
}
There are many ways to communicate between view models and a lot of points what the point is the best. You can see how it is done:
using MVVMLight
in Prism
by Caliburn
In my view, the best approach is using EventAggregator pattern of Prism framework. The Prism simplifies MVVM pattern. However, if you have not used Prism, you can use Rachel Lim's tutorial - simplified version of EventAggregator pattern by Rachel Lim.. I highly recommend you Rachel Lim's approach.
If you use Rachel Lim's tutorial, then you should create a common class:
public static class EventSystem
{...Here Publish and Subscribe methods to event...}
And publish an event into your OptionViewModel:
eventAggregator.GetEvent<ChangeStockEvent>().Publish(
new TickerSymbolSelectedMessage{ StockSymbol = “STOCK0” });
then you subscribe in constructor of another your MainViewModel to an event:
eventAggregator.GetEvent<ChangeStockEvent>().Subscribe(ShowNews);
public void ShowNews(TickerSymbolSelectedMessage msg)
{
// Handle Event
}
The Rachel Lim's simplified approach is the best approach that I've ever seen. However, if you want to create a big application, then you should read this article by Magnus Montin and at CSharpcorner with an example.
Update: For versions of Prism later than 5 CompositePresentationEvent is depreciated and completely removed in version 6, so you will need to change it to PubSubEvent everything else can stay the same.
I have done a big MVVM application with WPF. I have a lot of windows and I had the same problem. My solution maybe isn't very elegant, but it works perfectly.
First solution: I have done one unique ViewModel, splitting it in various file using a partial class.
All these files start with:
namespace MyVMNameSpace
{
public partial class MainWindowViewModel : DevExpress.Mvvm.ViewModelBase
{
...
}
}
I'm using DevExpress, but, looking your code you have to try:
namespace MyVMNameSpace
{
public partial class MainWindowViewModel : BindableBase
{
...
}
}
Second solution: Anyway, I have also a couple of different ViewModel to manage some of these windows. In this case, if I have some variables to read from one ViewModel to another, I set these variables as static.
Example:
public static event EventHandler ListCOMChanged;
private static List<string> p_ListCOM;
public static List<string> ListCOM
{
get { return p_ListCOM; }
set
{
p_ListCOM = value;
if (ListCOMChanged != null)
ListCOMChanged(null, EventArgs.Empty);
}
}
Maybe the second solution is simplier and still ok for your need.
I hope this is clear. Ask me more details, if you want.
I'm not a MVVM pro myself, but what I've worked around with problems like this is,
having a main class that has all other view models as properties, and setting this class as data context of all the windows, I don't know if its good or bad but for your case it seems enough.
For a more sophisticated solution see this
For the simpler one,
You can do something like this,
public class MainViewModel : BindableBase
{
FirstViewModel firstViewModel;
public FirstViewModel FirstViewModel
{
get
{
return firstViewModel;
}
set
{
firstViewModel = value;
}
}
public SecondViewModel SecondViewModel
{
get
{
return secondViewModel;
}
set
{
secondViewModel = value;
}
}
SecondViewModel secondViewModel;
public MainViewModel()
{
firstViewModel = new FirstViewModel();
secondViewModel = new SecondViewModel();
}
}
now you have to make another constructor for your OptionWindow passing a view model.
public SecondWindow(BindableBase viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
this is to make sure that both windows work on the same instance of a view model.
Now, just wherever you're opening the second window use these two lines
var window = new SecondWindow((ViewModelBase)this.DataContext);
window.Show();
now you're passing the First Window's view model to the Second window, so that they work on the same instance of the MainViewModel.
Everything is done, just you've to address to binding as
<TextBlock FontSize="{Binding FirstViewModel.EditorFontSize}"></TextBlock>
<TextBlock FontSize="{Binding SecondViewModel.EditorFontSize}"></TextBlock>
and no need to say that the data context of First window is MainViewModel
In MVVM, models are the shared data store. I would persist the font size in the OptionsModel, which implements INotifyPropertyChanged. Any viewmodel interested in font size subscribes to PropertyChanged.
class OptionsModel : BindableBase
{
public int FontSize {get; set;} // Assuming that BindableBase makes this setter invokes NotifyPropertyChanged
}
In the ViewModels that need to be updated when FontSize changes:
internal void Initialize(OptionsModel model)
{
this.model = model;
model.PropertyChanged += ModelPropertyChanged;
// Initialize properties with data from the model
}
private void ModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(OptionsModel.FontSize))
{
// Update properties with data from the model
}
}
I'm new to WPF and I've come up with a solution to this and I'm curious of more knowledgeable people's thoughts about what's right and wrong with it.
I have an Exams tab and a Templates tab. In my simple proof of concept, I want each tab to "own" an Exam object, and to be able to access the other tab's Exam.
I define the ViewModel for each tab as static because if it's a normal instance property, I don't know how one tab would get the actual instance of the other tab. It feels wrong to me, though it's working.
namespace Gui.Tabs.ExamsTab {
public class GuiExam: INotifyPropertyChanged {
private string _name = "Default exam name";
public string Name {
get => _name;
set {
_name = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName="") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public partial class ExamsHome : Page {
public ExamsHome() {
InitializeComponent();
DataContext = ViewModel;
}
public static readonly ExamsTabViewModel ViewModel = new ExamsTabViewModel();
}
public class ExamsTabViewModel {
public GuiExam ExamsTabExam { get; set; } = new GuiExam() { Name = "Exam from Exams Tab" };
public GuiExam FromTemplatesTab { get => TemplatesHome.ViewModel.TemplatesTabExam; }
}
}
namespace Gui.Tabs.TemplatesTab {
public partial class TemplatesHome : Page {
public TemplatesHome() {
InitializeComponent();
DataContext = ViewModel;
}
public static readonly TemplatesTabViewModel ViewModel = new TemplatesTabViewModel();
}
public class TemplatesTabViewModel {
public GuiExam TemplatesTabExam { get; set; } = new GuiExam() { Name = "Exam from Templates Tab" };
public GuiExam FromExamTab { get => ExamsHome.ViewModel.ExamsTabExam; }
}
}
And then everything is accessible in the xaml:
TemplatesHome.xaml (excerpt)
<StackPanel Grid.Row="0">
<Label Content="From Exams Tab:"/>
<Label FontWeight="Bold" Content="{Binding FromExamTab.Name}"/>
</StackPanel>
<StackPanel Grid.Row="1">
<Label Content="Local Content:"/>
<TextBox Text="{Binding TemplatesTabExam.Name, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" Width="200" FontSize="16"/>
</StackPanel>
ExamsHome.xaml (excerpt)
<StackPanel Grid.Row="0">
<Label Content="Local Content:"/>
<TextBox Text="{Binding ExamsTabExam.Name, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" Width="200" FontSize="16"/>
</StackPanel>
<StackPanel Grid.Row="1">
<Label Content="From Templates Tab:"/>
<Label FontWeight="Bold" Content="{Binding FromTemplatesTab.Name}"/>
</StackPanel>

Why can't I use this command in my resource dictionary?

I cannot for the life of me figure out why I cannot create my class in this dictionary. Intellisense isn't picking up my WindowCommand<T> class. I checked the Assembly name and it appears to be correct, no typos in the namespace either. What's making it choke?
WindowCommand.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using Ninject;
using Premier;
using Premier.View;
namespace Premier.Command
{
public class WindowCommand<T> : Command where T : Window
{
private Func<bool> focus;
private int instantiationCount;
public bool IsDialog { get; set; }
public bool Multiple { get; set; }
public WindowCommand()
{
}
public override bool CanExecute(object parameter)
{
return true;
}
public override void Execute(object parameter)
{
var instantiatedOnce = instantiationCount > 0;
if (!Multiple && instantiatedOnce)
{
focus();
return;
}
instantiationCount++;
var w = App.Kernel.Get<T>();
w.Closed += (s, e) => instantiationCount--;
focus = w.Focus;
if (IsDialog)
w.ShowDialog();
else
w.Show();
}
}
}
Windows.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:c="clr-namespace:Premier.Command;assembly=PremierAutoDataExtractor"
xmlns:v="clr-namespace:Premier.View;assembly=PremierAutoDataExtractor"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<c:WindowCommand x:Key="ReportsPurchased" x:TypeArguments="v:PurchasedReportsView" />
</ResourceDictionary>
x:TypeArguments XAML directive is not supported in XAML 2006 (xml namespace http://schemas.microsoft.com/winfx/2006/xaml/presentation) on non-root XAML elements. If you want to use x:TypeArguments on a non-root XAML element, you should use XAML2009 (xml namespace http://schemas.microsoft.com/netfx/2009/xaml/presentation). However, again it is only supported for non-complied loose XAML.
Text from MSDN Page:
In WPF and when targeting .NET Framework 4, you can use XAML 2009
features together with x:TypeArguments but only for loose XAML (XAML
that is not markup-compiled). Markup-compiled XAML for WPF and the
BAML form of XAML do not currently support the XAML 2009 keywords and
features. If you need to markup compile the XAML, you must operate
under the restrictions noted in the "XAML 2006 and WPF Generic XAML
Usages" section.
So, I am afraid, you cannot use your WindowCommand in a resource dictionary.
Link to MSDN page for more information on x:TypeArguments directive.

Setting Caliburn.Micro ControlContent from another ViewModel

I'm new to Caliburn.Micro (and MVVM for that matter) and I'm trying to Activate a screen with my conductor located in ShellViewModel from a button within a sub-viewmodel (one called by the conductor). All the tutorials I've seen have buttons in the actual shell that toggle between so I'm a little lost.
All the ViewModels share the namespace SafetyTraining.ViewModels
The ShellViewModel (first time ever using a shell so I might be using it in the wrong manner)
public class ShellViewModel : Conductor<object>.Collection.OneActive, IHaveDisplayName
{
public ShellViewModel()
{
ShowMainView();
}
public void ShowMainView()
{
ActivateItem(new MainViewModel());
}
}
ShellView XAML
<UserControl x:Class="SafetyTraining.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<ContentControl x:Name="ActiveItem" />
</DockPanel>
MainViewModel - the main screen (does correctly display).
public class MainViewModel : Screen
{
public void ShowLoginPrompt()
{
LoginPromptViewModel lg = new LoginPromptViewModel();//This does happen
}
}
MainView XAML
<Button cal:Message.Attach="[Event Click] = [ShowLoginPrompt]">Login</Button>
LoginPromptViewModel
public class LoginPromptViewModel : Screen
{
protected override void OnActivate()
{
base.OnActivate();
MessageBox.Show("Hi");//This is for testing - currently doesn't display
}
}
EDIT Working Code:
Modified Sniffer's code a bit to properly fit my structure. Thanks :)
var parentConductor = (Conductor<object>.Collection.OneActive)(this.Parent);
parentConductor.ActivateItem(new LoginPromptViewModel());
You are doing everything correctly, but you are missing one thing though:
public void ShowLoginPrompt()
{
LoginPromptViewModel lg = new LoginPromptViewModel();//This does happen
}
You are creating an instance of LoginPromptViewModel, but you are not telling the conductor to activate this instance, so it's OnActivate() method is never called.
Now before I give you a solution I should suggest a couple of things:
If you are using the MainViewModel to navigate between different view-models then it would be appropriate to make MainViewModel a conductor itself.
If you aren't using it like that, then perhaps you should put the button that navigates to the LoginPromptViewModel in the ShellView itself.
Now back to your problem, since your MainViewModel extends Screen then it has a Parent property which refers to the Conductor, so you can do it like this:
public void ShowLoginPrompt()
{
LoginPromptViewModel lg = new LoginPromptViewModel();//This does happen
var parentConductor = (Conductor)(lg.Parent);
parentConductor.Activate(lg);
}

Categories

Resources