i need to create a GUI-DLL-Component which should be used as a dialog.
This dialog performs some calculations and then searches the database for the result (let's say the result is a number).
The result is bound to the view via a public property of the viewmodel.
The user want to intantiate an object of this GUI component and open the dialog, after the calculation is done the user need to access the result in a later point of time.
What i want to ask is how to access the (Result) public property of the viewmodel after intantiating the object because i don't know how to do it in a MVVM way. My temproral solution is to cast the data context of the window in code behind and then access its public property. But it's not MVVM (In this case the dialog is derived from window class. And after the method .showdialog() is called there is no way to access the public property of the window's viewmodel).
How can i do this in a MVVM manner?
Thank you very much for your help :).
Best regards,
Minh
Edit:
here is my code:
XAML:
<catel:DataWindow x:Class="WpfApplication3.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"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="clr-namespace:WpfApplication3.ViewModels"
d:DesignHeight="273"
d:DesignWidth="457"
SizeToContent="WidthAndHeight">
<Window.DataContext>
<viewmodel:MainWindowViewModel></viewmodel:MainWindowViewModel>
</Window.DataContext>
<Grid>
<Button Content="Calc 1+1"
Height="39"
Name="button1"
Width="87"
Command="{Binding CalcCmd}"/>
<TextBox Height="23"
HorizontalAlignment="Left"
Name="textBox1"
VerticalAlignment="Top"
Width="87"
Margin="174,152,0,0"
Text="{Binding Result}"/>
<Label Content="Result:"
Height="28"
HorizontalAlignment="Left"
Margin="111,152,0,0"
Name="label1"
VerticalAlignment="Top"
Width="46" />
</Grid>
</catel:DataWindow>
Code Behind:
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Catel.Windows;
using WpfApplication3.ViewModels;
namespace WpfApplication3
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : DataWindow
{
public MainWindow()
{
InitializeComponent();
}
public MainWindow(MainWindowViewModel mainWindowViewModel)
: base(mainWindowViewModel)
{
InitializeComponent();
}
//Temporal solution
public string Result
{
get {
MainWindowViewModel vm = (MainWindowViewModel)this.DataContext;
return vm.Result;
}
}
}
}
ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Catel.MVVM;
using Catel.Data;
namespace WpfApplication3.ViewModels
{
/// <summary>
/// name view model.
/// </summary>
public class MainWindowViewModel : ViewModelBase
{
#region Fields
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="MainWindowViewModel"/> class.
/// </summary>
public MainWindowViewModel()
{
registeringCommands();
}
#endregion
#region Properties
/// <summary>
/// Gets the title of the view model.
/// </summary>
/// <value>The title.</value>
public override string Title { get { return "MyMainWindow"; } }
/// <summary>
/// Gets or sets the property value.
/// </summary>
public string Result
{
get { return GetValue<string>(ResultProperty); }
set { SetValue(ResultProperty, value); }
}
/// <summary>
/// Register the Result property so it is known in the class.
/// </summary>
public static readonly PropertyData ResultProperty =
RegisterProperty("Result", typeof(string), null);
#endregion
#region Commands
/// <summary>
/// Gets the name command.
/// </summary>
public Command CalcCmd { get; private set; }
/// <summary>
/// Method to invoke when the name command is executed.
/// </summary>
private void execute_CalcCmd()
{
try {
Result = (1 + 1).ToString();
}
catch(Exception ex)
{
throw;
//log
}
}
#endregion
#region Methods
private void registeringCommands()
{
CalcCmd = new Command(execute_CalcCmd);
}
#endregion
}
}
You can pass a view model instance to the ShowDialog method of the UIVisualizerService. This view model will still be available after the window is closed and this is the same view model used on the DataWindow. You can simply use the value in the calling view model.
If the result needs to be used in a lot of classes, it is better to create a dedicated class / service for this and register it in the ServiceLocator. For example, a Settings : ISettings which you modify in the DataWindow and you can read anywhere by querying the ServiceLocator / DependencyResolver or by letting them get injected in the classes where you need the information.
Related
So i'm working on a simple WPF app that should read informations from a "dedicated page " on our LAN and output them more efficiently. So now i'm trying to make the labels content update automatically after an ip to get datas from is selected. The plan is to basically refresh the page every minute or so to update the output.
Problem is I tryed a lot of solutions about multithreading but still get the same error : InvalidOperationException: The calling thread cannot access this object because a different thread owns it.
basically what i understand from that is that the mainWindows thread owns the labels' contents so that i can't update them after reading from said file/string.
Here's my xaml for the window:
<Window x:Class="PartCountBello.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:PartCountBello"
mc:Ignorable="d"
Title="Controllo Macchine" Height="337.42" Width="366.586"
ResizeMode="CanMinimize">
<Grid Margin="0,10,2,12">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="0*"/>
</Grid.ColumnDefinitions>
<ComboBox x:Name="cmbNomiMacchine" HorizontalAlignment="Left"
Margin="44,74,0,0" VerticalAlignment="Top" Width="129" Height="22"
SelectionChanged="cmbNomiMacchine_SelectionChanged"/>
<Label x:Name="lblNomeDato" Content="PartCount :"
HorizontalAlignment="Left" Margin="44,162,0,0" VerticalAlignment="Top"
Height="26" Width="129"/>
<Label x:Name="lblPartCount" Content="" HorizontalAlignment="Left"
VerticalAlignment="Top" Margin="178,162,0,0" Height="26" Width="144"
RenderTransformOrigin="0.071,0.731"/>
<Label x:Name="lblSelectInfos" Content="Selezionare macchina"
HorizontalAlignment="Left" Margin="44,43,0,0" VerticalAlignment="Top"
Width="129" Height="26"/>
<Label x:Name="lblLavoro" Content="Mansione : "
HorizontalAlignment="Left" Margin="44,210,0,0" VerticalAlignment="Top"
Width="129"/>
<Label x:Name="lblMansione" Content="" HorizontalAlignment="Left"
Margin="178,210,0,0" VerticalAlignment="Top" Width="144"/>
<Button x:Name="btnRefresh" Content="Aggiorna"
HorizontalAlignment="Left" Margin="247,74,0,0" VerticalAlignment="Top"
Width="75" Height="22" Click="btnRefresh_Click"/>
<Label x:Name="lblMoreInfos" Content="" HorizontalAlignment="Left"
Margin="10,241,0,0" VerticalAlignment="Top" Width="339" Height="35"/>
</Grid>
</Window>
and here is my mainWindows Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace PartCountBello
{
/// <summary>
/// Logica di interazione per MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
List<string> datas = new List<string> { }; //list that contains machines names and relatives ips. each ip is in the previous index...
//respect to the machine.
public MainWindow()
{
InitializeComponent();
try
{
ReadFile rf = new ReadFile(); //creates the file reader we 're using to...
datas = rf.getListFromFile("MyIPsFile"); //get all our datas from our file.. Not posting the real file name here cuz you never know. still contains only the ips to connect to.
for(int i = 2;i<=datas.Count-1;i+=3)
{
cmbNomiMacchine.Items.Add(datas[i]);
}
}
catch (Exception ex)
{
lblMoreInfos.Content = ex.Message;
}
finally
{
}
}
/// <summary>
/// when the selection is changed to an item it will update the labels contents through a method in the class activityChecker.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cmbNomiMacchine_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
updateLabelsContent();
}
/// <summary>
/// gets the index the item selected in the combobox has in the file to pass it to the fileread and get the ip.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
private int getSelectedItemIndex(int index)
{
int FIRST_REAL_INDEX = 2;
int realIndex;
if (index == 0)
return 0;
else
return realIndex=FIRST_REAL_INDEX+index*2;
}
/// <summary>
/// checks if the machine we are trying to connect to is on, if so updates the dedicated lables' content, else prints a simple message down below.
/// </summary>
private void updateLabelsContent()
{
string toShow;//the auxiliary string we are going to use to output on labels.
ActivityChecker ac = new ActivityChecker();
lblMoreInfos.Content = "";
if (getSelectedItemIndex(cmbNomiMacchine.SelectedIndex) != 0)
{
if (PingHost(datas[getSelectedItemIndex(cmbNomiMacchine.SelectedIndex) - 1]))
{
toShow = ac.getPartCount(datas[getSelectedItemIndex(cmbNomiMacchine.SelectedIndex)]);
lblPartCount.Content = toShow;
toShow = ac.getJob(datas[getSelectedItemIndex(cmbNomiMacchine.SelectedIndex)]);
lblMansione.Content = toShow;
}
else
{
lblMoreInfos.Content = "La macchina è al momento spenta.";
}
}
else
{
lblMansione.Content = "";
lblPartCount.Content = "";
}
}
/// <summary>
/// updates the content of the machine dedicated labels.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnRefresh_Click(object sender, RoutedEventArgs e)
{
updateLabelsContent();
}
/// <summary>
/// allows to verify if the machine we are trying to connect to is on before we actually try to, avoids some freezes.
/// </summary>
/// <param name="nameOrAddress"></param>
/// <returns></returns>
public static bool PingHost(string nameOrAddress)
{
bool pingable = false;
Ping pinger = new Ping();
try
{
PingReply reply = pinger.Send(nameOrAddress);
pingable = reply.Status == IPStatus.Success;
}
catch (PingException)
{
// Discard PingExceptions and return false;
}
return pingable;
}
}
}
Ok i found a solution.
added this to mainwindows.cs:
/// <summary>
/// private method that manages the thread for the automatic update.
/// </summary>
private void updateLabelsContentThread()
{
while(true)
{
Thread.Sleep(TimeSpan.FromSeconds(10));
Dispatcher.Invoke(new Action(() => { updateLabelsContent(); }));
}
}
and changed the event method for machine selection to this.
/// <summary>
/// when the selection is changed to an item it will update the labels contents through a method in the class activityChecker.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cmbNomiMacchine_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
updateLabelsContent();
Thread t = new Thread(updateLabelsContentThread);
t.Start();
}
works perfectly for me, leaving this here in case it helps someone.
I am not advocating this WinForms-like approach, there are probably better solutions using bindings, but it would require a lot of changes. If you need to do it this way and now:
public partial class MainWindow : Window
{
....
private readonly SynchronizationContext uiContext;
public MainWindow()
{
InitializeComponent();
//controls created on a specific thread can only be modified by that thread.
//(99.99999%) of the time controls are created and modified on the UI thread
//SynchronizationContext provides a way to capture and delegate work to the UI thread (it provides more functionality than this, but in your case this is what interests you)
//We capture the UI Synchronization Context so that we can queue items for execution to the UI thread. We know this is the UI thread because we are in the constructor for our main window
uiContext = SynchronizationContext.Current;
....
}
private void cmbNomiMacchine_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
UpdateOnUIThread();
}
...............
///I Chose to write another method for clarity, feel free to rework the code anyway you like. Ideally you want to only delegate short work to the UI thread (say textbox.Text = "". This is just here to show the concept
private void UpdateOnUIThread()
{
//Post is asynchronous so will give controll back immediately. If you want synchronous operation, use Send
uiContext.Post(new SendOrPostCallback((o) => { updateLabelsContent(); }), null);
}
..............
}
}
<Window x:Class="GuessFigure.PlayingGameWindow"
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:GuessFigure"
mc:Ignorable="d"
Title="PlayingGame" Height="300" Width="300">
<Grid
xmlns:c="clr-namespace:GuessFigure.Model">
<Grid.Resources>
<c:Round x:Key="round"/>
</Grid.Resources>
<Grid.DataContext>
<Binding Source=" {StaticResource round}" />
</Grid.DataContext>
<TextBlock x:Name="tbTime" HorizontalAlignment="Left" Margin="108,202,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top"/>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="123,86,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
<TextBlock x:Name="roundNumber" HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Text="{Binding Path=Number}"/>
</Grid>
</Window>
Round.cs:
using GuessFigure.Model.Factory;
using Ninject;
using Ninject.Modules;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ninject.Planning.Bindings;
namespace GuessFigure.Model
{
/// <summary>
/// 回合
///
/// 每回合限时30s,超时直接进入下一回合
///
/// 题目(数字)由FigureFactory类生产
/// </summary>
///
class Round
{
/// <summary>
/// 回合数
///
/// 一共五回合,每回合出一道猜数字的题目
/// </summary>
internal int Number { get; } = 1;
/// <summary>
/// 生产题目的工厂
/// </summary>
private FigureFactory figureFactory;
[Inject]
internal void SetFigureFactory(FigureFactory figureFactory)
{
this.figureFactory = figureFactory;
}
/// <summary>
/// 调用<see cref=">FigureFactory"/>
/// </summary>
/// <returns>
/// 当前回合的题目。
/// </returns>
public int[] GetCurrentRoundFigures()
{
return figureFactory.Produce(Number);
}
}
class RoundModule : NinjectModule
{
public override void Load()
{
Bind<FigureFactory>().To<FigureFactoryRound1>().When(request=>request.ParentRequest.Target.Type.GetField("number").Equals(1));
Bind<FigureFactory>().To<FigureFactoryRound2>().When(request => request.Target.Type.GetField("number").Equals(2));
Bind<FigureFactory>().To<FigureFactoryRound3>().When(request => request.Target.Type.GetField("number").Equals(3));
Bind<FigureFactory>().To<FigureFactoryRound4>().When(request => request.Target.Type.GetField("number").Equals(4));
Bind<FigureFactory>().To<FigureFactoryRound5>().When(request => request.Target.Type.GetField("number").Equals(5));
}
}
}
I want to bind class Round's property Number to the second TextBlock in Grid and show it to user. However, there was nothing show after I made these code.
What's wrong in my code?
System.Windows.Data Error: 40 : BindingExpression path error: 'Number'
property not found on 'object' ''String' (HashCode=-1455514144)'.
BindingExpression:Path=Number; DataItem='String'
(HashCode=-1455514144); target element is 'TextBlock'
(Name='roundNumber'); target property is 'Text' (type 'String')
Related question may help: WPF/XAML Property not found on 'object'
The property you bind to has to be public, your property is internal. See here.
The properties you use as binding source properties for a binding must be public properties of your class. Explicitly defined interface properties cannot be accessed for binding purposes, nor can protected, private, internal, or virtual properties that have no base implementation.
Also, this:
request.Target.Type.GetField("number").Equals(2)
Will not work since:
Number has a capital N
is a property
GetField("Number") returns aFieldInfo` instance, not the value.
So what you can try is this:
request.Target.Type.GetProperty("number").GetValue(request.Target, null).Equals(2)
First of all, my code has multiple wrongs, and thank Maarten who indicated that
The property you bind to has to be public, your property is internal.
However that was not enough and the problem was still there. I solved my problem at last, by remove the blank space before {StaticResource round} in <Binding Source=" {StaticResource round}" />.
Now the binding runs as I expect.
Your are trying to bind an attribute to a internal property which is wrong. The attribute can only bind to an public property.
Public property is recommend to capitalize first letter.
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 trying to grasp the new compiled bindings, but right at the start I get stopped by this simple problem.
I have Hub control with one HubSection. The content of this section is an ItemsControl that needs to bind to view models' observable collection. I can't get this binding to work as I expect it to.
<Pivot x:Name="rootPivot" Style="{StaticResource TabsStylePivotStyle}">
<PivotItem>
<Hub>
<HubSection Header="News">
<DataTemplate x:DataType="local:HomePage">
<ItemsControl ItemsSource="{x:Bind ViewModel.NewsItems, Mode=OneWay}" />
ViewModel property is just a property and is instantiated before InitializeComponents() call. NewsItems is observable collection inside view model that is filled after the page has loaded - asynchronously (web request).
What am I doing wrong here?
EDIT: Code-behind
HomePage.xaml.cs
/// <summary>
/// Home pag view.
/// </summary>
public sealed partial class HomePage : Page
{
/// <summary>
/// Initializes a new instance of the <see cref="HomePage"/> class.
/// </summary>
public HomePage()
{
// Retrieve view model
this.ViewModel = ViewModelResolver.Home;
// Trigger view model loaded on page loaded
this.Loaded += (sender, args) => this.ViewModel.LoadedAsync();
this.InitializeComponent();
}
/// <summary>
/// Gets the view model.
/// </summary>
/// <value>
/// The view model.
/// </value>
public IHomeViewModel ViewModel { get; }
}
HomePageViewModel.cs
/// <summary>
/// Home view model.
/// </summary>
public sealed class HomeViewModel : IHomeViewModel
{
/// <summary>
/// Occurs on page loaded.
/// </summary>
public async Task LoadedAsync()
{
// Retrieve news items
var news = await new NewsService().GetNewsAsync();
foreach (var newsItem in news)
this.NewsItems.Add(newsItem);
}
/// <summary>
/// Gets the news items.
/// </summary>
/// <value>
/// The news items.
/// </value>
public ObservableCollection<IFeedItem> NewsItems { get; } = new ObservableCollection<IFeedItem>();
}
This is indeed an interesting question. I guess the issue is that, unlike typical DataTemplate like the following (see its parent ListView is binding to some known data Model.Items)
<ListView ItemsSource="{x:Bind Model.Items}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Item">
<Grid>
<TextBlock Text="{x:Bind Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
your top level DataTemplate however, doesn't know where the data comes from.
So the fix is to tell the HubSection to bind the right data - in this case, the HomePage.xaml.cs instance. So, try adding this to your Hub
<Hub DataContext="{x:Bind}">
Or simply add
this.InitializeComponent();
this.DataContext = this;
Either way should fix your issue.
I am learning WPF with MVVM Design pattern and trying to understand how to get some things done outside of the code behind.
I have a login page, pictured below.
I have a password control I took from http://www.wpftutorial.net/PasswordBox.html.
I would for now for the case of simple understanding, like to ask you if my code is in the right class/set correctly to abide to MVVVM regulations with seperation of concerns.
I currently have an if statement to check if the details match a name string and a password string.
The code is in the code behind. I just wonder if this is correct with regards to MVVM. I wonder how you implement this in a ViewModel?
private void OK_Click(object sender, RoutedEventArgs e)
{
if (emp.Name == "ep" && emp.Password == "pass")
{
MessageBox.Show("namd and Pw accepted");
//open new page
var HomeScreen = new HomeScreen();
HomeScreen.Show();
}
else
{
//deny access
MessageBox.Show("Incorrect username and password");
}
}
Instead of implementing the button click handler in the code behind, use an ICommand and bind it to the button's event in XAML.
Here is a really great tutorial that got me starting in MVVM:
WPF Apps With The Model-View-ViewModel Design Pattern
[Edited to add sample code]
Heres a simple code example to do just what your example does, but in MVVM style and without any code-behind code at all.
1) Create a new WPF Solution, for this small example I named it simply "WpfApplication".
2) Edit the code of the automatically created MainWindow.xaml:
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:WpfApplication"
Title="MainWindow" Height="234" Width="282">
<!-- Create the ViewModel as the initial DataContext -->
<Window.DataContext>
<viewModel:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="70,31,0,0"
Name="textBox1"
VerticalAlignment="Top"
Width="120"
Text="{Binding Path=Name}"/>
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="70,72,0,0"
Name="textBox2"
VerticalAlignment="Top"
Width="120"
Text="{Binding Path=Password}" />
<Label Content="Name"
Height="28"
HorizontalAlignment="Left"
Margin="22,29,0,0"
Name="label1"
VerticalAlignment="Top" />
<Label Content="PW"
Height="28"
HorizontalAlignment="Left"
Margin="22,70,0,0"
Name="label2"
VerticalAlignment="Top" />
<Button Content="OK"
Height="23"
HorizontalAlignment="Left"
Margin="70,119,0,0"
Name="button1"
VerticalAlignment="Top"
Width="120"
Command="{Binding Path=LoginCommand}"
CommandParameter="{Binding Path=.}"
/>
</Grid>
</Window>
(Ignore the Width, Height, Margin values, these are just copied & pasted from my designer and were quick & dirty adjusted to roughly look like your screenshot ;-) )
3) Create the Command class that will handle your log in logic. Note that I did not implement it as a RelayCommand like in Josh Smith's tutorial but it would be easy to modify the code accordingly:
namespace WpfApplication
{
using System;
using System.Windows;
using System.Windows.Input;
/// <summary>
/// Checks the user credentials.
/// </summary>
public class LoginCommand : ICommand
{
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public void Execute(object parameter)
{
MainWindowViewModel viewModel = parameter as MainWindowViewModel;
if (viewModel == null)
{
return;
}
if (viewModel.Name == "ep" && viewModel.Password == "pass")
{
MessageBox.Show("namd and Pw accepted");
//open new page
var HomeScreen = new HomeScreen();
HomeScreen.Show();
}
else
{
//deny access
MessageBox.Show("Incorrect username and password");
}
}
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public bool CanExecute(object parameter)
{
// Update this for your application's needs.
return true;
}
public event EventHandler CanExecuteChanged;
}
}
4) Now add the ViewModel that will communicate with the View and provide it the command interface and values:
namespace WpfApplication
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Input;
/// <summary>
/// TODO: Update summary.
/// </summary>
public class MainWindowViewModel : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Signal that the property value with the specified name has changed.
/// </summary>
/// <param name="propertyName">The name of the changed property.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion Implementation of INotifyPropertyChanged
#region Backing Fields
/// <summary>
/// Gets or sets the value of Name.
/// </summary>
private string name;
/// <summary>
/// Gets or sets the value of Password.
/// </summary>
private string password;
/// <summary>
/// Gets or sets the value of LoginCommand.
/// </summary>
private LoginCommand loginCommand;
#endregion Backing Fields
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="MainWindowViewModel"/> class.
/// </summary>
public MainWindowViewModel()
{
this.loginCommand = new LoginCommand();
}
#endregion Constructor
#region Properties
/// <summary>
/// Gets or sets the name of the user.
/// </summary>
public string Name
{
get
{
return this.name;
}
set
{
if (this.name == value)
{
return;
}
this.name = value;
this.OnPropertyChanged("Name");
}
}
/// <summary>
/// Gets or sets the user password.
/// </summary>
public string Password
{
get
{
return this.password;
}
set
{
if (this.password == value)
{
return;
}
this.password = value;
this.OnPropertyChanged("Password");
}
}
/// <summary>
/// Gets or sets the command object that handles the login.
/// </summary>
public ICommand LoginCommand
{
get
{
return this.loginCommand;
}
set
{
if (this.loginCommand == value)
{
return;
}
this.loginCommand = (LoginCommand)value;
this.OnPropertyChanged("LoginCommand");
}
}
#endregion Properties
}
}
5) Finally, do not forget to add an additional Window HomeScreen that will be opened by the LoginCommand to the solution. :-)
To implement that, you should bind the button's command like this -
<Button Content ="OK" Command = {Binding OKCommand} />
In your ViewModel, create an ICommand property for this binding like this -
public class MyViewModel() : INotifyPropertyChanged
{
ICommand _OKCommand;
public ICommand OKCommad
{
get { return _OKCommand; }
set { _OKCommand = value; PropertyChanged(OKCommad); }
}
public MyViewModel()
{
this.OKCommand += new DelegateCommand(OKCommand_Execute);
}
public void OKCommand_Execute()
{
// Code for button click here
}
}
Also note that for using this delegate command, you need to add reference to Microsoft.Practices.Prism.dll