I have Resharper on my Visual Studio, so setting the DataContext in a WPF xaml file is very useful to get to the IntelliSense. Something like this:
<UserControl ... blah blah namespace stuff >
<UserControl.DataContext>
<viewModels:FooViewModel />
</UserControl.DataContext>
<Label Text={Binding SomeText} /> <!-- I can get IntelliSense for SomeText -->
...
However, I want to do a bit of initialisation on the ViewModel/DataContext before I give it to the view. Something like this:
public class FooViewModel : INotifyPropertyChanged {
private Foo Model { get; set; }
public FooViewModel(Foo model) {
Model = model;
}
// Only here for WPF compatibility - I realise this might be the root of my problems :)
public FooViewModel()
: this (new Foo) {
}
public string SomeText { ... }
...
}
public class ShowFooer {
public void ShowFoo() {
Foo model = ... // get the foo from where ever
FooViewModel viewModel = new FooViewModel(model);
FooWindow window = new FooWindow(viewModel); // Push the data context into the constructor
view.Show();
...
}
}
When I debug through this I find that 2 FooViewModels are created - one where I push the context into the constructor, and one that is automatically created in the InitialiseComponents of the FooWindow.
tl;dr: Is there a way to say to WPF that I already have a data context and that I am just using the data context xaml tag to get IntelliSense? Is there a way from stopping it creating a new context? At the moment I am just commenting out the data context lines when I compile and uncomment them when I am coding.
Or alternative question: Is there a way to set the data context after the constructor, but have all the fields initialised from the second data context without having to do a long list of OnPropertyChanged calls?
Yes. You can use this:
<UserControl
xmlns:viewModels="clr-namespace:..."
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance viewModels:FooViewModel,
IsDesignTimeCreatable=True}" ...> ...
With this you get autocompletion for all vm properties.
The following XAML markup will cause an instance of the view model to get created at runtime using the default parameterless constructor:
<UserControl.DataContext>
<viewModels:FooViewModel />
</UserControl.DataContext>
So you should remove this piece of markup (and probably also the default constructor) if you construct the view model yourself programmatically using the other overload that takes a model parameter.
You can then set a design time data context as suggested by Schwammkopf to get intellisense in Visual Studio.
Related
I have a Window with a Grid, which has a "MainWindowViewModel" set as its DataContext
<Grid x:Name="MainGrid">
<Grid.DataContext>
<view:MainWindowViewModel/>
</Grid.DataContext>
<!-- ... -->
</Grid>
This MainGrid has two SubGrids (not named) and one of them contains a Frame which displays Pages.
The Pages displayed have other ViewModels set as their DataContext.
<Page.DataContext>
<view:AddOrderViewModel/>
</Page.DataContext>
In the MainWindowViewModel I have a Property "User". I want to access this Property from the ViewModel of the Page.
Is that even possible (without using "code behind"). I dont really know where to start since I dont know how to get the FrameworkElement using the ViewModel from within the ViewModel (I guess from there its only handling the visual tree?)
Any help, or push in the right direction would be greatly appreciated. Also if you have a better idea of how to pass the property from one ViewModel to the other, feel free to share :)
Thanks
I would suggest to try MVVM Light's Messenger.
It is thoroughly enough explained here
You create a class where you place the object property you want to send between ViewModels
public class MessageClassName
{
public object MyProperty { get; set;}
}
Assuming you want to send the property from ViewModel1 to ViewModel2, you create a method in ViewModel1
private void SendProperty(object myProperty)
{
Messenger.Default.Send<MessageClassName>(new MessageClassName() { MyProperty = myProperty });
}
Then you are calling it from your code when you want it to be sent.
SendProperty(_myProperty);
In the constructor of ViewModel2 you register to that message
public ViewModel2()
{
Messenger.Default.Register<MessageClassName>(this, (message) =>
{
ReceiveProperty(message.MyProperty);
)};
}
Then also in ViewModel2 you define the method ReceiveProperty
private void ReceiveProperty(object myProperty)
{
...Do whatever with myProperty here...
}
Note that you need to add
using GalaSoft.MvvmLight.Messaging;
in both ViewModel1 and ViewModel2 classes
In one hand I have my model which had to collect data from several files and build a oriented object database, and in another I have my interface in which I want to display data from my database . So I use binding but my ComboBox, etc.. remain empty. I have the feeling that my database is built then erased when the interface is launched. Here's the code of my Main defined in the App.xaml.cs:
public partial class App : Application
{
[STAThread]
public static void Main()
{
var application = new App();
application.InitializeComponent();
DirectoryInfo dir = new DirectoryInfo("P:\\....");
Model model = new Model(dir);
model.entityBox.initialize();
application.Run();
}
}
Code for binding in MainWindow.xaml:
<Window.DataContext>
<local:EntityBox></local:EntityBox>
</Window.DataContext>
<Grid>
<ComboBox x:Name="critereComboBox" ItemsSource="{Binding Criteres}"/>
In EntityBox.cs:
private List<string> _criteres = new List<string>();
public void initialize()
{
_criteres.Add("TXC");
_criteres.Add("TYC");
_criteres.Add("TZC");
_criteres.Add("MXC");
_criteres.Add("MYC");
_criteres.Add("MZC");
}
public List<string> Criteres
{
get{ return _criteres; }
}
You need to initialize combobox inside context class, because when you use XAML to bind your data context, the context class is created independently by XAML, the model creation in Main function has literally no effect to your Control.
You also need to implement INotifyPropertyChanged to your Model (ViewModel?) class. I am also suggest you to step into MVVM approach.
I suppose it's a one-way binding. A short answer is to use ObservableCollection
private ObservableCollection<string> _criteres = new ObservableCollection<string>();
As it will notify when you call Add, but you might need to call them in UIDispatcher.
How to perform XAML conversion (eg whole grid or viewbox) to png file?
I need to do this from the ViewModel level.
Link to the example function that I can not call in ViewModel because I do not have access to the object.
Is there a simple and pleasant way?
The view will be responsible for actually exporting the elements that you see on the screen according to the answer you have linked to.
The view model should initialize the operation though. It can do so in a number of different ways.
One option is to send a loosely coupled event or message to the view using an event aggregator or a messenger. Please refer to the following blog post for more information on subject: http://blog.magnusmontin.net/2014/02/28/using-the-event-aggregator-pattern-to-communicate-between-view-models/.
Another option is to inject the view model with a loose reference to the view. The view implements an interface and uses either constructor injection or property injection to inject itself into the view model, e.g.:
public interface IExport
{
void Export(string filename);
}
public partial class MainWindow : Window, IExport
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel(this);
}
public void Export(string filename)
{
//export...
}
}
public class ViewModel
{
private readonly IExport _export;
public ViewModel(IExport export)
{
_export = export;
}
public void DoExport()
{
//...
_export.Export("pic.png");
}
}
This way the view model only knows about and have a depdenceny upon an interface. It's has no dependency upon the view and in your unit tests you could easily provide a mock implementation of the IExport interface.
The view model will and should never have any access to the actual elements to be exported though. These belong to the view.
You need something like Interaction - a way for VM to take something from view. If you don't want to install a whole new framework for that, just use Func property:
Your VM:
public Func<string, Bitmap> GetBitmapOfElement {get;set;}
...
//in some command
var bmp = GetBitmapOfElement("elementName");
Then, in your view you have assign something to that property:
ViewModel.GetBitmapOfElement = elementName =>
{
var uiElement = FindElementByName(elementName); // this part you have figure out or just always use the same element
return ExportToPng(FrameworkElement element); // this is the function form the link form your answer modified to return the bitmap instead of saving it to file
}
If you need it async, just change property type to Func<string, Task<Bitmap>> and assign async function in your view
What about dependency properties? Consider the following class that is used to passing data (the data may be a stream or whatever you want):
public class Requester
{
public event Action DataRequested;
public object Data { get; set; }
public void RequestData() => DataRequested?.Invoke();
}
Then you create a usercontrol and register a dependency property of type Requester:
public partial class MyUserControl : UserControl
{
public static readonly DependencyProperty RequesterProperty
= DependencyProperty.Register("Requester", typeof(Requester), typeof(MainWindow),
new PropertyMetadata(default(Requester), OnRequesterChanged));
public MyUserControl()
{
InitializeComponent();
}
public Requester Requester
{
get => (Requester) GetValue(RequesterProperty);
set => SetValue(RequesterProperty, value);
}
private static void OnRequesterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> ((Requester) e.NewValue).DataRequested += ((MyUserControl) d).OnDataRequested;
private void OnDataRequested()
{
Requester.Data = "XD";
}
}
And your view model would look something like this:
public class MainWindowViewModel
{
public Requester Requester { get; } = new Requester();
public void RequestData() => Requester.RequestData();
}
In XAML you simply bind the dependency property from your control to the property in your view model:
<Window x:Class="Test.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:Test"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<local:MyUserControl Requester="{Binding Requester}"/>
</Grid>
</Window>
I have an HelloWorldWPFApplication class with the following method:
public override void Run()
{
var app = new System.Windows.Application();
app.Run(new ApplicationShellView());
}
The ApplicationShellView has the following XAML:
<winbase:ApplicationShell x:Class="HelloWorldWPFApplication.View.ApplicationShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:winbase="clr-namespace:Framework.Presentation.Control.Window;assembly=Framework"
xmlns:vm="clr-namespace:HelloWorldWPFApplication.ViewModel"
Title="{Binding WindowTitle, Mode=OneWay}">
<Window.DataContext>
<vm:ApplicationShellViewModel />
</Window.DataContext>
</winbase:ApplicationShell>
If my ViewModel (ApplicationShellViewModel) has the following method, the window will have the title set to "Test":
public string WindowTitle
{
get
{
return "Test";
}
}
My problem is that I want to set the title based on properties within the HelloWorldWPFApplication class. I added the following to the HelloWorldWPFApplication's base class (which uses the INotifyPropertyChanged interface):
private WpfApplicationBase<WpfApplicationDataBase> applicationModel;
public WpfApplicationBase<WpfApplicationDataBase> Application
{
get { return this.applicationModel; }
set { this.Set<WpfApplicationBase<WpfApplicationDataBase>>(ref this.applicationModel, value); }
}
So effectively, I plan on reusing the existing HelloWorldWPFApplication object as the model (in MVVM).
I changed the WindowTitle property as follows:
public string WindowTitle
{
get
{
return String.Format("{0} {1}",
this.applicationModel.Data.FullName,
this.applicationModel.Data.ReleaseVersion).Trim();
}
}
Of course, at this stage my project creates a window without a title, as the application field has not been set. I don't want to create a new application object within the view model as one already exists. I want to use this existing object. What is the best way to achieve this?
I am very new to MVVM/WPF - and from my basic understanding of MVVM I don't want to put any code-behind in the view. I could have a static field set on a static class to the application object, and then assign this field in my view model (this works, but not sure having "global" variables is the best approach).
I have also tried creating the view model before showing the window, but have encountered a problem I have yet to solve. In this implementation my run method appears as follows:
public override void Run()
{
var window = new ApplicationShell(); // inherits from System.Windows.Window
var vm = new ApplicationShellViewModel();
vm.Application = this; // this line won't compile
window.DataContext = vm;
this.Data.WpfApplication.Run(window);
}
I get a compile error:
Error 1 Cannot implicitly convert type 'HelloWorldWPFApplication.Program.HelloWorldApplication' to 'Framework.Business.Logic.Program.Application.WpfApplicationBase'
I'm confused with the error as my HelloWorldWPFApplication class inherits from WpfApplicationBase:
public class HelloWorldApplication<T> : WpfApplicationBase<T>
where T : HelloWorldApplicationData
Additionally, HelloWorldApplicationData inherits from WpfApplicationDataBase.
I get the pretty much the same problem with the following implementation:
public override void Run()
{
var window = new ApplicationShell();
var vm = new ApplicationShellViewModel();
var app = new HelloWorldApplication<HelloWorldApplicationData>();
vm.Application = app; // Cannot implicitly convert type error again
window.DataContext = vm;
this.Data.WpfApplication.Run(window);
}
Exact error:
Error 1 Cannot implicitly convert type 'HelloWorldWPFApplication.Program.HelloWorldApplication' to 'Framework.Business.Logic.Program.Application.WpfApplicationBase'
First off, the "Application" class in WPF should be used for one thing, and one thing only: starting the program. It is not a model.
That said, I would just pass everything in sequence (this can apply to a proper model as well):
MyViewModel viewmodel = new MyViewModel(this);
var app = new System.Windows.Application();
app.Run(new ApplicationShellView(viewmodel));
Of course, remove the data context set from XAML. This does require modifying your code behind to accept the VM object and set it to the DataContext in your constructor, but thats a standard way of passing the VM to the View.
You could also use a Service Locator to find your model, or a number of other ways. Unfortunately, its hard to say which one is right, since your model is so weird.
As a complete aside; the title of your program is very much a part of the View, and probably doesn't need to be bound at all (your name is static, so making your application class the model isn't buying you anything).
Okay. So what I need to do is to initialize a ViewModel using a constructor. The problem is I can't create the constructor due lack of knowledge. I'm new to MVVM (or c# in general for that matter) and had to get some help to implement this code:
public class ViewModel
{
private static ViewModel instance = new ViewModel();
public static ViewModel Instance
{
get
{
return instance;
}
}
}
However, I fail to create a constructor to place this code.
DataContext = ViewModel.Instance
It is meant to go into two different pages to pass a value between TextBoxes.
I'm also confused as to whether I should put the ViewModel in both the main window and the page or in just one of the two.
So, anyone can help?
Follow this pattern:
This part is how your model classes should look like,
Even if you use entity framework to create your model they inherit INPC.. so all good.
public class Model_A : INotifyPropertyChanged
{
// list of properties...
public string FirstName {get; set;}
public string LastName {get; set;}
// etc...
}
each view model is a subset of information to be viewed, so you can have many view models for the same model class, notice that in case your make the call to the parameter-less c-tor you get auto instance of a mock model to be used in the view model.
public class ViewModel_A1 : INotifyPropertyChanged
{
public Model_A instance;
public ViewModel()
{
instance = new instance
{ //your mock value for the properties..
FirstName = "Offer",
LastName = "Somthing"
};
}
public ViewModel(Model_A instance)
{
this.instance = instance;
}
}
And this is for your view, if you view in the ide designer you will have a mock view model to show.
public class View_For_ViewModelA1
{
public View_For_ViewModel_A1()
{
//this is the generated constructor already implemented by the ide, just add to it:
DataContext = new ViewModel_A1();
}
public View_For_ViewModel_A1(ViewModel_A1 vm)
{
DataContext = vm;
}
}
XAML Side:
<Window x:Class="WpfApplication1.View_For_ViewModel_A1"
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:ViewModel="clr-namespace:WpfApplication1"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"
d:DataContext="{d:DesignInstance ViewModel:ViewModel_A1, IsDesignTimeCreatable=True}"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
</Grid>
</Window>
In a more advanced scenario you would want to have a single view model class to relate to several model classes.. but you always should set a view to bind to a single view model.
if you need to kung-fu with your code - make sure you do that in your view model layer.
(i.e. creating a view-model that have several instances of different model types)
Note: This is not the complete pattern of mvvm.. in the complete pattern you can expose command which relate to methods in your model via your view-model and bind-able to your view as well.
Good luck :)
I basically follow this pattern:
public class ViewModelWrappers
{
static MemberViewModel _memberViewModel;
public static MemberViewModel MemberViewModel
{
get
{
if (_memberViewModel == null)
_memberViewModel = new MemberViewModel(Application.Current.Resources["UserName"].ToString());
return _memberViewModel;
}
}
...
}
To bind this to a page is:
DataContext = ViewModelWrappers.MemberViewModel;
And if I'm using more than 1 ViewModel on the page I just bind to the wrapper.
DataContext = ViewModelWrappers;
If you or anybody else, who's new to the MVVM, gets stuck here, for example at the "INotifyPropertyChanged could not be found". I recommend trying some example-MVVM's or tutorials.
Some I found useful:
http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial
https://www.youtube.com/watch?v=EpGvqVtSYjs&index=1&list=PL356CA0B2C8E7548D