How do we take screenshot in mvvm windows phone application.
I tried the below code but it doesn't work in mvvm app as it requires uielement .Any suggests how to do this in mvvm app ?
var screenshot = new WriteableBitmap(this, null);
var screenshotname = String.Format("Screenshooter_{0}", DateTime.Now.Ticks);
using (var ms = new MemoryStream())
{
screenshot.SaveJpeg(ms, 480, 800, 0, 85);
ms.Seek(0, SeekOrigin.Begin);
var library = new MediaLibrary();
var pic = library.SavePicture(screenshotname, ms);
}
UtilityLib.ShowMessageDialog(string.Concat("Screenshot saved as ", screenshotname));
Try this:
We should register each service in ViewModelLocator, as following:
using Cimbalino.Phone.Toolkit.Services;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
/// <summary>
/// This class contains static references to all the view models in the
/// application and provides an entry point for the bindings.
/// </summary>
public class ViewModelLocator
{
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (!SimpleIoc.Default.IsRegistered<IScreenshotService>())
{
SimpleIoc.Default.Register<IScreenshotService, ScreenshotService>();
}
SimpleIoc.Default.Register<MainViewModel>();
}
/// <summary>
/// Gets the main view model.
/// </summary>
/// <value>
/// The main view model.
/// </value>
public MainViewModel MainViewModel
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
var viewModelLocator = (ViewModelLocator)App.Current.Resources["Locator"];
viewModelLocator.MainViewModel.Cleanup();
}
}
Then we should implement the MainViewModel as following:
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using Cimbalino.Phone.Toolkit.Services;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight;
/// <summary>
/// This class contains properties that the main View can data bind to.
/// </summary>
public class MainViewModel : ViewModelBase
{
/// <summary>
/// The screenshot service
/// </summary>
private readonly IScreenshotService _screenshotService;
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(IScreenshotService screenshotService)
{
_screenshotService = screenshotService;
TakeScreenshotCommand = new RelayCommand(TakeScreenshot);
}
/// <summary>
/// Gets the take screenshot command.
/// </summary>
/// <value>
/// The take screenshot command.
/// </value>
public ICommand TakeScreenshotCommand { get; private set; }
/// <summary>
/// Calls to.
/// </summary>
private void TakeScreenshot()
{
_screenshotService.TakeScreenshot("CimbalinoScreenshot");
}
}
for connect view model with the page we should add the ViewModelLocator instance in App.xaml:
XAML
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
and add the binding in main page like:
XAML
DataContext="{Binding MainViewModel,
Source={StaticResource Locator}}"
The MainPage.xaml can be the following:
XAML
<phone:PhoneApplicationPage x:Class="CimbalinoSample.MainPage"
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:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
DataContext="{Binding MainViewModel,
Source={StaticResource Locator}}"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
Orientation="Portrait"
SupportedOrientations="Portrait"
shell:SystemTray.IsVisible="True"
mc:Ignorable="d">
<!-- LayoutRoot is the root grid where all page content is placed -->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- TitlePanel contains the name of the application and page title -->
<StackPanel x:Name="TitlePanel"
Grid.Row="0"
Margin="12,17,0,28">
<TextBlock Margin="12,0"
Style="{StaticResource PhoneTextTitle2Style}"
Text="Cimbalino Sample" />
<TextBlock Margin="9,-7,0,0"
Style="{StaticResource PhoneTextTitle2Style}"
Text="ScreenshotService" />
</StackPanel>
<!-- ContentPanel - place additional content here -->
<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<TextBlock TextWrapping="Wrap">This samples has the goal to show how to use Cimbalino Windows Phone Toolkit Media Library - ScreenshotService</TextBlock>
<Button Margin="0,219,0,293"
Command="{Binding TakeScreenshotCommand}"
Content="Take Screenshot" />
</Grid>
</Grid>
</phone:PhoneApplicationPage>
Source : http://code.msdn.microsoft.com/wpapps/How-to-use-Cimbalino-749562db
Related
What I'm doing:
On my DataGrid I have a context menu to add Template items. The MenuItems are created dynamically using the ItemsSource property of the parent MenuItem. The ItemsSource is an ObservableCollection of my template objects. I want to get the Header of the dynamic MenuItems from the collection object properties, but execute a command from my main ViewModel. The main ViewModel is bound to the DataContext of the root Grid.
My issue:
The MenuItems are created properly and I can also bind the header to a property of an object in the collection. But the Command binding does not work. I can see an error in the output window:
"System.Windows.Data Error: 4 : Cannot find source for binding with reference..."
Here is my code reduced to the issue (using MVVM light):
MainWindow.xaml:
<Window x:Class="TapTimesGui.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="600" Width="1000">
<!-- root grid -->
<Grid x:Name="RootGrid" DataContext="{Binding Source={StaticResource Locator}, Path=Main}">
<!-- Jobs overview -->
<DataGrid Grid.Column="0">
<DataGrid.ContextMenu>
<ContextMenu>
<!-- following works fine -->
<MenuItem Header="Basic command test" Command="{Binding AddTemplateJobCommand}" CommandParameter="Basic command test"/>
<MenuItem Header="Add template..." ItemsSource="{Binding JobTemplates}">
<MenuItem.ItemTemplate>
<DataTemplate>
<!-- following command does not work System.Windows.Data Error -->
<MenuItem Header="{Binding Name}" Command="{Binding ElementName=RootGrid, Path=DataContext.AddTemplateJobCommand}" CommandParameter="{Binding Name}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
<!--<MenuItem.ItemContainerStyle> also does not work
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"></Setter>
<Setter Property="Command" Value="{Binding ElementName=RootGrid, Path=DataContext.SaveDayToNewFileCommand}"></Setter>
</Style>
</MenuItem.ItemContainerStyle>-->
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Grid>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace TapTimesGui
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
MainViewModel.cs:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using GalaSoft.MvvmLight.Messaging;
using System.Collections.ObjectModel;
using System.Windows.Input;
using TapTimesGui.Services;
namespace TapTimesGui.ViewModel
{
/// <summary>
/// ...
/// </summary>
public class MainViewModel : ViewModelBase
{
#region Properties and backing fields
/// <summary>
/// Templates for jobs
/// </summary>
public ObservableCollection<JobTemplate> JobTemplates
{
get
{
return _jobTemplates;
}
}
private readonly ObservableCollection<JobTemplate> _jobTemplates = new ObservableCollection<JobTemplate>();
#endregion Properties and backing fields
#region ICommand properties
public ICommand AddTemplateJobCommand { get; private set; }
#endregion ICommand properties
#region Constructors
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
// assign commands
AddTemplateJobCommand = new RelayCommand<string>(AddTemplateJob);
// populate data on start
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
}
else
{
//TODO test for templates
JobTemplate tmpJobTemplate = new JobTemplate();
tmpJobTemplate.Name = "Template 1";
tmpJobTemplate.Template = "TestCustomer1 AG";
JobTemplates.Add(tmpJobTemplate);
tmpJobTemplate = new JobTemplate();
tmpJobTemplate.Name = "Template 2";
tmpJobTemplate.Template = "TestCustomer2 AG";
JobTemplates.Add(tmpJobTemplate);
}
}
#endregion Constructors
#region Command methods
private void AddTemplateJob(string name)
{
//TODO implement
Messenger.Default.Send<NotificationMessage>(new NotificationMessage(name));
}
#endregion Command methods
}
}
ViewModelLocator.cs:
/*
In App.xaml:
<Application.Resources>
<vm:ViewModelLocator xmlns:vm="clr-namespace:TapTimesGui"
x:Key="Locator" />
</Application.Resources>
In the View:
DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}"
You can also use Blend to do all this with the tool's support.
See http://www.galasoft.ch/mvvm
*/
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
//using Microsoft.Practices.ServiceLocation; TODO http://www.mvvmlight.net/std10 chapter known issues
using CommonServiceLocator;
using GalaSoft.MvvmLight.Messaging;
using System;
using System.Windows;
namespace TapTimesGui.ViewModel
{
/// <summary>
/// This class contains static references to all the view models in the
/// application and provides an entry point for the bindings.
/// </summary>
public class ViewModelLocator
{
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
////if (ViewModelBase.IsInDesignModeStatic)
////{
//// // Create design time view services and models
//// SimpleIoc.Default.Register<IDataService, DesignDataService>();
////}
////else
////{
//// // Create run time view services and models
//// SimpleIoc.Default.Register<IDataService, DataService>();
////}
SimpleIoc.Default.Register<MainViewModel>();
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageHandler);
Messenger.Default.Register<Exception>(this, ExceptionMessageHandler);
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
private void NotificationMessageHandler(NotificationMessage message)
{
//MessageBox.Show(message.Notification);
System.Diagnostics.Debug.WriteLine(message.Notification);
MessageBox.Show(message.Notification);
}
private void ExceptionMessageHandler(Exception ex)
{
MessageBox.Show(ex.ToString(), "Exception", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
JobTemplate.cs:
using System.ComponentModel;
namespace TapTimesGui.Services
{
/// <summary>
/// Named TapTimesGui.Model.Job template
/// </summary>
/// <remarks>Can be used e.g. to save Templates of specific objects</remarks>
public class JobTemplate : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
private string _name = "";
public string Template //Type was not string originally, it was if my Model object
{
get
{
//TODO proper cloning
return _template;
}
set
{
//TODO proper cloning
_template = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Template)));
}
}
private string _template = "Template";
}
}
Versions:
Visual Studio Professional 2015
.NET Framework 4.5.2
MvvmLight 5.4.1.1
CommonServiceLocator 2.0.5 (required for MvvmLight)
I already tried to find a solution for some time. Thanks in advance for your help.
You can access the ViewModelLocator inside the ItemTemplate as well.
<DataGrid Grid.Column="0">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Add template..." ItemsSource="{Binding JobTemplates}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Name}" Command="{Binding Source={StaticResource Locator}, Path=Main.AddTemplateJobCommand}" CommandParameter="{Binding Name}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
A more general approach without MVVM light could be using a binding proxy.
I wanted to embed a winform chart control in a WPF window which shall be bound to an observablecollection filled by entering data in a WPF DataGrid.
The observablecollection is needed because i fill it by using a WPF-DataGrid in which i can insert or update data.
So i added to my WPF-project the following dependencies:
- System.Windows.Forms
- System.Windows.Forms.DataVisualization
For a first test i hardcoded in the constructor of the WPF window some data in the observablecollection and bound the chart control.
In that case the display in the chart works fine.
But in the final version i want to insert and/or update data in the DataGrid and the chart shall display that data all at once.
Is it possible to manage that?
Here is the code for the window and the classes as example.
The WPF window MainWindow.xaml:
<Window x:Class="StepFunctions.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:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
xmlns:winformchart="clr-namespace:System.Windows.Forms.DataVisualization.Charting;assembly=System.Windows.Forms.DataVisualization"
xmlns:local="clr-namespace:StepFunctions"
mc:Ignorable="d"
Title="StepFunctions"
Height="350"
Width="525">
<Grid x:Name="maingrid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--DataGrid for insert and update of step function data-->
<DataGrid x:Name="grd_stepdata"
Grid.Row="0"
Grid.Column="0"
Margin="5"
AutoGenerateColumns="False"
CanUserAddRows="True"
RowEditEnding="grd_stepdata_RowEditEnding"
ItemsSource="{Binding StepDataSource, NotifyOnSourceUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Columns>
<DataGridTextColumn x:Name="col_LowerComparer" Binding="{Binding LowerComparer, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Lower comparer"/>
<DataGridTextColumn x:Name="col_LowerBound" Binding="{Binding LowerBound, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Lower bound"/>
<DataGridTextColumn x:Name="col_StepValue" Binding="{Binding StepValue, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Value"/>
</DataGrid.Columns>
</DataGrid>
<!--Chart for displaying the step function data-->
<WindowsFormsHost x:Name="host"
Grid.Row="0"
Grid.Column="1"
Margin="5">
<winformchart:Chart x:Name="myWinformChart"
Dock="Fill">
<winformchart:Chart.Series>
<winformchart:Series Name="series" ChartType="Line"/>
</winformchart:Chart.Series>
<winformchart:Chart.ChartAreas>
<winformchart:ChartArea/>
</winformchart:Chart.ChartAreas>
</winformchart:Chart>
</WindowsFormsHost>
<!--Button for test-->
<Button x:Name="btn_do"
Grid.Row="2"
Grid.Column="0"
Margin="5"
Click="btn_do_Click">Do it</Button>
</Grid>
</Window>
The code-behind of MainWindow.xaml:
using StepFunctions.ViewModels;
using System.Windows;
using System.Windows.Controls;
namespace StepFunctions
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainWindowViewModel vm = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = vm;
// These lines are just for the first test.
// Normally these lines would be out-commented.
AddStepdata();
ChartDataRefresh();
}
// When the user leaves a DataGrid-row after insert or update the chart shall be refreshed.
private void grd_stepdata_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
ChartDataRefresh();
}
private void AddStepdata()
{
vm.StepDataSource.Add(new StepData("<", 10, 1));
vm.StepDataSource.Add(new StepData("<", 100, 2));
vm.StepDataSource.Add(new StepData("<", 1000, 3));
}
private void ChartDataRefresh()
{
myWinformChart.DataSource = vm.StepDataSource;
myWinformChart.Series["series"].XValueMember = "LowerBound";
myWinformChart.Series["series"].YValueMembers = "StepValue";
}
/// <summary>
/// For testing the refresh of the chart after the window was loaded.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_do_Click(object sender, RoutedEventArgs e)
{
AddStepdata();
ChartDataRefresh();
}
}
}
The viewmodel:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace StepFunctions.ViewModels
{
public class MainWindowViewModel : INotifyPropertyChanged
{
/// <summary>
/// Eventhandler for signalising that a property has changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<StepData> stepdataSource = new ObservableCollection<StepData>();
public ObservableCollection<StepData> StepDataSource
{
get { return stepdataSource; }
set
{
stepdataSource = value;
RaisePropertyChanged("StepDataSource");
}
}
/// <summary>
/// Informs the target which is bound to a property, that it's source was changed and that it shall update.
/// </summary>
/// <param name="propertyName">The name of the property.</param>
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And finally the class StepData which is the base for the observablecollection:
namespace StepFunctions.ViewModels
{
/// <summary>
/// Class for data of stepfunctions.
/// </summary>
public class StepData
{
/// <summary>
/// The constructor.
/// </summary>
public StepData()
{
// Do nothing
}
public StepData(string lowerComparer, double lowerBound, double stepValue)
{
LowerComparer = lowerComparer;
LowerBound = lowerBound;
StepValue = stepValue;
}
public string LowerComparer { get; set; }
public double LowerBound { get; set; }
public double StepValue { get; set; }
}
}
I got it!
The chart has to be generated in code-behind, not in the XAML.
So the method ChartDataRefresh has to look like that:
private void ChartDataRefresh()
{
Chart myWinformChart = new Chart();
myWinformChart.Dock = System.Windows.Forms.DockStyle.Fill;
Series mySeries = new Series("series");
mySeries.ChartType = SeriesChartType.Line;
myWinformChart.Series.Add(mySeries);
ChartArea myArea = new ChartArea();
myWinformChart.ChartAreas.Add(myArea);
myWinformChart.DataSource = vm.StepDataSource;
myWinformChart.Series["series"].XValueMember = "LowerBound";
myWinformChart.Series["series"].YValueMembers = "StepValue";
host.Child = myWinformChart;
}
While entering data in the WPF-DataGrid the Winform chart control is refreshed and the data displayed as a line for checking if the given data is correct.
In MS VS 2015 Professional I develop C# WPF MVVM application using Catel as MVVM framework. My problem is I don't know how to realize switching among multiple views in one window using buttons. Below I briefly describe my application. The MainWindow has three buttons
<catel:Window x:Class="FlowmeterConfigurator.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com"
ResizeMode="CanResize">
<catel:StackGrid x:Name="LayoutRoot">
<catel:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
</catel:StackGrid.RowDefinitions>
<ToolBar>
<Button Name="btnConnectDisconnect" Content="Connect/Disconnect"/>
<Button Name="btnFieldSettings" Content="Field Settings"/>
<Button Name="btnCalibration" Content="Flowmeter Calibration"/>
</ToolBar>
</catel:StackGrid>
</catel:Window>
Application MainWindow has a ViewModel. For brevity I don't show it here. In addition to MainWindow there are three views in my application: ConnectDisconnectView, CalibrationView and FieldSettingsView. For brevity I show here only one of them (FieldSettingsView) because all of others are created in the same manner on the base of catel:UserControl.
<catel:UserControl x:Class="FlowmeterConfigurator.Views.FieldSettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com">
<catel:StackGrid>
<catel:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</catel:StackGrid.RowDefinitions>
<catel:StackGrid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</catel:StackGrid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Flowmeter Serial Number"/>
<TextBox Name="SerialNumber" Grid.Row="0" Grid.Column="1"/>
</catel:StackGrid>
</catel:UserControl>
Each of these views has a Model. I show here only one of these Models because all of them created in the same manner.
using Catel.Data;
namespace FlowmeterConfigurator.Models
{
/// <summary>
/// Field Settings Model.
/// </summary>
public class FieldSettingsModel : SavableModelBase<FieldSettingsModel>
{
/// <summary>
/// Returns flowmeter serial number.
/// </summary>
public string SerialNumber
{
get { return GetValue<string>(SerialNumberProperty); }
set { SetValue(SerialNumberProperty, value); }
}
/// <summary>
/// Register SerialNumber property.
/// </summary>
public static readonly PropertyData SerialNumberProperty = RegisterProperty("SerialNumber", typeof(string), null);
}
}
Each of these views has a ViewModel. I show here only one of these ViewModels because all of them created in the same manner.
using Catel;
using Catel.Data;
using Catel.MVVM;
using FlowmeterConfigurator.Models;
namespace FlowmeterConfigurator.ViewModels
{
/// <summary>
/// Field settings ViewModel.
/// </summary>
public class FieldSettingsViewModel : ViewModelBase
{
/// <summary>
/// Creates a FieldSettingsViewModel instance.
/// </summary>
/// <param name="fieldSettingsModel">Field settings Model.</param>
public FieldSettingsViewModel(FieldSettingsModel fieldSettingsModel)
{
Argument.IsNotNull(() => fieldSettingsModel);
FieldSettings = fieldSettingsModel;
}
/// <summary>
/// Returns or sets Field Settings Model.
/// </summary>
[Model]
public FieldSettingsModel FieldSettings
{
get { return GetValue<FieldSettingsModel>(FieldSettingsProperty); }
set { SetValue(FieldSettingsProperty, value); }
}
/// <summary>
/// Here I register FieldSettings property.
/// </summary>
public static readonly PropertyData FieldSettingsProperty = RegisterProperty("FieldSettings", typeof(FieldSettingsModel), null);
/// <summary>
/// Returns or sets flowmeter serial number.
/// </summary>
[ViewModelToModel("FieldSettings")]
public string SerialNumber
{
get { return GetValue<string>(SerialNumberProperty); }
set { SetValue(SerialNumberProperty, value); }
}
/// <summary>
/// Here I register SerialNumber property.
/// </summary>
public static readonly PropertyData SerialNumberProperty = RegisterProperty("SerialNumber", typeof(string), null);
}
}
Directly after my application loading, ConnectDisconnectView must be displayed. And then user can switch the views at will using the buttons on MainWindow toolbar. The switching among the Views must be in the following manner: if (for example) the current displayed view is "ConnectDisconnectView" and user presses "Field Settings" button then "ConnectDisconnectView" view must disappear from MainWindow and "FieldSettingsView" view must appear and must be displayed in MainWindow. And so on. That is when pressed appropriate button in MainWindow toolbar (for example "Flowmeter Calibration") the appropriate view (CalibrationView) must be displayed in MainWindow and other views must not be displayed. How can I realize this capability in my application? Your help will be appreciate highly.
P.S. Of course as you see the number and content of Views are reduced here for brevity and clarity. In real world the number of Views in my application is about 20 - 25 and they must contain complex graphics and table information.
First I show you xaml code:
<catel:Window.Resources>
<catel:ViewModelToViewConverter x:Key="ViewModelToViewConverter" />
</catel:Window.Resources>
<catel:StackGrid x:Name="LayoutRoot">
<ContentControl Content="{Binding CurrentPage, Converter={StaticResource ViewModelToViewConverter}}" />
<ToolBar>
<Button Name="btnConnectDisconnect" Command={Binding Connect} Content="Connect/Disconnect"/>
<Button Name="btnFieldSettings" Command={Binding Field} Content="Field Settings"/>
<Button Name="btnCalibration" Command={Binding Calibration} Content="Flowmeter Calibration"/>
</ToolBar>
</catel:StackGrid>
Then in c# code you need this:
using Catel.Data;
using Catel.MVVM;
using System.Threading.Tasks;
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
this.Connect = new Command(HandleConnectCommand);
this.Field = new Command(HandleFieldCommand);
this.Calibration = new Command(HandleCalibrationCommand);
this.CurrentPage = new ConnectViewModel();
}
/// <summary>
/// Gets or sets the CurrentPage value.
/// </summary>
public IViewModel CurrentPage
{
get { return GetValue<IViewModel>(CurrentPageProperty); }
set { SetValue(CurrentPageProperty, value); }
}
/// <summary>
/// Register the CurrentPage property so it is known in the class.
/// </summary>
public static readonly PropertyData CurrentPageProperty = RegisterProperty("CurrentPage", typeof(IViewModel), null);
public Command Connect { get; private set; }
public Command Field { get; private set; }
public Command Calibration { get; private set; }
protected override async Task InitializeAsync()
{
await base.InitializeAsync();
// TODO: subscribe to events here
}
protected override async Task CloseAsync()
{
// TODO: unsubscribe from events here
await base.CloseAsync();
}
private void HandleCalibrationCommand()
{
this.CurrentPage = new CalibrationViewModel();
}
private void HandleFieldCommand()
{
this.CurrentPage = new FieldViewModel();
}
private void HandleConnectCommand()
{
this.CurrentPage = new ConnectViewModel();
}
}
When you start your application CurrentPage is to be loaded with data context ConnectViewModel(). And then with commands from buttons you can change date context for another view model.
One way to solve this problem is using regions from Prism. Catel provides an extension for Prism so you can activate view models in specific regions.
I'm developing a Windows 8.1 store app with C# and .NET Frameowork 4.5.1.
I'm trying to pass a PasswordBox as a parameter on a button Command. I've tested the following code on WPF and it works.
MainViewModel class:
public class MainViewModel : ViewModelBase
{
private RelayCommand<PasswordBox> doLoginCommand;
/// <summary>
/// The <see cref="UserName" /> property's name.
/// </summary>
public const string UserNamePropertyName = "UserName";
private string _userName = string.Empty;
/// <summary>
/// Sets and gets the UserName property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public string UserName
{
get
{
return _userName;
}
set
{
Set(UserNamePropertyName, ref _userName, value);
}
}
/// <summary>
///
/// </summary>
public RelayCommand<PasswordBox> DoLoginCommand
{
get { return doLoginCommand; }
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
////if (IsInDesignMode)
////{
//// // Code runs in Blend --> create design time data.
////}
////else
////{
//// // Code runs "for real"
////}
this.doLoginCommand = new RelayCommand<PasswordBox>((pb) => ExecuteDoLogin(pb), (pb) => CanDoLogin(pb));
//this.doLoginCommand = new RelayCommand<PasswordBox>((pb) => ExecuteDoLogin(pb));
}
private void ExecuteDoLogin(object parameter)
{
PasswordBox passwordBox = parameter as PasswordBox;
Debug.WriteLine(_userName);
Debug.WriteLine(passwordBox.Password);
}
private bool CanDoLogin(object parameter)
{
Debug.WriteLine("CanDoLogin");
PasswordBox passwordBox = parameter as PasswordBox;
return ((!string.IsNullOrEmpty(_userName)) &&
(!string.IsNullOrEmpty(passwordBox.Password)));
}
}
And its View:
<Page
x:Class="MyProject.W81.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyProject.W81"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
DataContext="{Binding MainViewModel, Source={StaticResource Locator}}"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="33*"/>
<ColumnDefinition Width="77*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" HorizontalAlignment="Center" Height="400" Margin="0" VerticalAlignment="Center" Width="600">
<TextBox
x:Name="userName"
TextWrapping="Wrap"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="247"
Margin="10,10,10,5"/>
<PasswordBox
x:Name="userPassword"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="250"
FontFamily="Global User Interface"
Margin="10,5"/>
<Button
x:Name="loginButton"
Content="Login"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Command="{Binding DoLoginCommand}"
CommandParameter="{Binding ElementName=userPassword}"
Margin="10,5" />
</StackPanel>
</Grid>
</Page>
But on Windows 8.1 it doesn't work. The problem is that loginButton is always disabled.
I have removed CanDoLogin on doLoginCommand creation and I can get _userName and userPassword.Password values.
Any advice? Do you know why loginButton is always disabled?
Don´t you have to call RaiseCanExecuteChanged on the command then something changes? Haven´t done any WPF in a long time but in apps I always call RaiseCanExecuteChanged.
Is this case I would call RaiseCanExecuteChanged on the PasswordChanged event.
I'm new to WP development and I have a problem I can't get fixed on my own.
I'm trying to set up a settings page. I copied most of it from that msdn article:
http://msdn.microsoft.com/en-us/library/windowsphone/develop/ff769510(v=vs.105).aspx#BKMK_CreatingaSettingsPageThatDoesNotRequireaConfirmationButton
The problem is connected to this line:
<phone:PhoneApplicationPage.Resources>
<local:Page1 x:Key="appSettings" />
</phone:PhoneApplicationPage.Resources>
It behaves completly random. Most of the time VS just crashes.
I am sure that this is because VS tries to connect to the IsolatedStorage. But the Emulator just closes the App when I try to access the page.
The full code of the settings page (Page1):
Page1.xaml:
<phone:PhoneApplicationPage
x:Class="MyApp.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyApp"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
shell:SystemTray.IsVisible="True">
<phone:PhoneApplicationPage.Resources>
<local:Page1 x:Key="appSettings" />
</phone:PhoneApplicationPage.Resources>
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="MYAPP" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="settings" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<Grid x:Name="ContentGrid" Grid.Row="1">
<CheckBox Content="CheckBox Setting" Height="Auto" HorizontalAlignment="Left" Margin="60,20,0,0" Name="checkBoxSetting" VerticalAlignment="Top"
IsChecked="{Binding Source={StaticResource appSettings}, Path=CheckBoxSetting, Mode=TwoWay}" />
<ListBox Height="140" HorizontalAlignment="Left" Margin="70,150,0,0" Name="listBoxSetting"
VerticalAlignment="Top" Width="360" SelectedIndex="{Binding Source={StaticResource appSettings}, Path=ListBoxSetting, Mode=TwoWay}">
<ListBoxItem Content="Times New Roman" FontSize="24" FontFamily="Times New Roman" />
<ListBoxItem Content="Arial" FontSize="24" FontFamily="Arial" />
<ListBoxItem Content="Comic Sans MS" FontSize="24" FontFamily="Comic Sans MS" />
</ListBox>
<RadioButton Content="Choice One" Height="Auto" HorizontalAlignment="Left" Margin="60,0,0,235" Name="radioButton1" VerticalAlignment="Bottom" GroupName="GroupOne" IsChecked="{Binding Source={StaticResource appSettings}, Path=RadioButton1Setting, Mode=TwoWay}" />
<RadioButton Content="Choice Two" Height="Auto" HorizontalAlignment="Left" Margin="60,350,0,0" Name="radioButton2" VerticalAlignment="Top" GroupName="GroupOne" IsChecked="{Binding Source={StaticResource appSettings}, Path=RadioButton2Setting, Mode=TwoWay}"/>
<RadioButton Content="Choice Three" Height="Auto" HorizontalAlignment="Left" Margin="60,400,0,0" Name="radioButton3" VerticalAlignment="Top" GroupName="GroupOne" IsChecked="{Binding Source={StaticResource appSettings}, Path=RadioButton3Setting, Mode=TwoWay}"/>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
And the Page1.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.IO.IsolatedStorage;
using System.Diagnostics;
namespace Schedule
{
public partial class Page1 : PhoneApplicationPage
{
// Our settings
IsolatedStorageSettings settings;
// The key names of our settings
const string CheckBoxSettingKeyName = "CheckBoxSetting";
const string ListBoxSettingKeyName = "ListBoxSetting";
const string RadioButton1SettingKeyName = "RadioButton1Setting";
const string RadioButton2SettingKeyName = "RadioButton2Setting";
const string RadioButton3SettingKeyName = "RadioButton3Setting";
const string UsernameSettingKeyName = "UsernameSetting";
const string PasswordSettingKeyName = "PasswordSetting";
// The default value of our settings
const bool CheckBoxSettingDefault = true;
const int ListBoxSettingDefault = 0;
const bool RadioButton1SettingDefault = true;
const bool RadioButton2SettingDefault = false;
const bool RadioButton3SettingDefault = false;
const string UsernameSettingDefault = "";
const string PasswordSettingDefault = "";
/// <summary>
/// Constructor that gets the application settings.
/// </summary>
public Page1()
{
InitializeComponent();
// Get the settings for this application.
try
{
settings = IsolatedStorageSettings.ApplicationSettings;
}
catch(System.IO.IsolatedStorage.IsolatedStorageException e)
{
MessageBox.Show(e.ToString());
}
}
/// <summary>
/// Update a setting value for our application. If the setting does not
/// exist, then add the setting.
/// </summary>
/// <param name="Key"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool AddOrUpdateValue(string Key, Object value)
{
bool valueChanged = false;
// If the key exists
if (settings.Contains(Key))
{
// If the value has changed
if (settings[Key] != value)
{
// Store the new value
settings[Key] = value;
valueChanged = true;
}
}
// Otherwise create the key.
else
{
settings.Add(Key, value);
valueChanged = true;
}
return valueChanged;
}
/// <summary>
/// Get the current value of the setting, or if it is not found, set the
/// setting to the default setting.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="Key"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
public T GetValueOrDefault<T>(string Key, T defaultValue)
{
T value;
// If the key exists, retrieve the value.
if (settings.Contains(Key))
{
value = (T)settings[Key];
}
// Otherwise, use the default value.
else
{
value = defaultValue;
}
return value;
}
/// <summary>
/// Save the settings.
/// </summary>
public void Save()
{
settings.Save();
}
/// <summary>
/// Property to get and set a CheckBox Setting Key.
/// </summary>
public bool CheckBoxSetting
{
get
{
return GetValueOrDefault<bool>(CheckBoxSettingKeyName, CheckBoxSettingDefault);
}
set
{
if (AddOrUpdateValue(CheckBoxSettingKeyName, value))
{
Save();
}
}
}
/// <summary>
/// Property to get and set a ListBox Setting Key.
/// </summary>
public int ListBoxSetting
{
get
{
return GetValueOrDefault<int>(ListBoxSettingKeyName, ListBoxSettingDefault);
}
set
{
if (AddOrUpdateValue(ListBoxSettingKeyName, value))
{
Save();
}
}
}
/// <summary>
/// Property to get and set a RadioButton Setting Key.
/// </summary>
public bool RadioButton1Setting
{
get
{
return GetValueOrDefault<bool>(RadioButton1SettingKeyName, RadioButton1SettingDefault);
}
set
{
if (AddOrUpdateValue(RadioButton1SettingKeyName, value))
{
Save();
}
}
}
/// <summary>
/// Property to get and set a RadioButton Setting Key.
/// </summary>
public bool RadioButton2Setting
{
get
{
return GetValueOrDefault<bool>(RadioButton2SettingKeyName, RadioButton2SettingDefault);
}
set
{
if (AddOrUpdateValue(RadioButton2SettingKeyName, value))
{
Save();
}
}
}
/// <summary>
/// Property to get and set a RadioButton Setting Key.
/// </summary>
public bool RadioButton3Setting
{
get
{
return GetValueOrDefault<bool>(RadioButton3SettingKeyName, RadioButton3SettingDefault);
}
set
{
if (AddOrUpdateValue(RadioButton3SettingKeyName, value))
{
Save();
}
}
}
/// <summary>
/// Property to get and set a Username Setting Key.
/// </summary>
public string UsernameSetting
{
get
{
return GetValueOrDefault<string>(UsernameSettingKeyName, UsernameSettingDefault);
}
set
{
if (AddOrUpdateValue(UsernameSettingKeyName, value))
{
Save();
}
}
}
/// <summary>
/// Property to get and set a Password Setting Key.
/// </summary>
public string PasswordSetting
{
get
{
return GetValueOrDefault<string>(PasswordSettingKeyName, PasswordSettingDefault);
}
set
{
if (AddOrUpdateValue(PasswordSettingKeyName, value))
{
Save();
}
}
}
}
}
Am I would be very happy about any help.
Thanks.
Instead of local:Page1, you're supposed to put a separate class (named AppSettings in the MSDN sample).
In your case, you have put an instance of Page1 in the XAML of... Page1! Therefore, when the runtime creates Page1, it parses the XAML, find this line of code, create the new Page1 instance, which will in turn create a Page1 instance, and so on...