Transfer data in WPF using MVVM pattern - c#

I'm stuck in this for too long and I just need someone to show me the direction...
The thing is that Im trying to develop using MVVM pattern and I can't seem to know how can I transfer some data from a method and bind it to my XAML. Also I have problems with setting up an interface ( INotifyPropertyChanged ) in all this structure. Could any of you show where does it have to be implemented?
I will try to explain on my code...
I have a DataModel that for example will be an API that will fetch some data from the web:
public class DataModel
{
public string apiResult = "null";
private void GetDataFromApi()
{
// Some web service
apiResult = "SOME RESULT FROM WEB API";
}
}
Now I have a ViewModel for the logic:
public class ViewModel
{
private DataModel dm = new DataModel();
public string ApiResult
{
get { return dm.apiResult; }
set { dm.apiResult = value; }
}
public void GetApi()
{
dm.GetDataFromApi();
}
}
And finaly the View:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFexample" x:Class="WPFexample.MainWindow"
DataContext="{Binding ''}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock HorizontalAlignment="Left"
Margin="110,126,0,0"
TextWrapping="Wrap"
Text="{Binding ApiResult}"
VerticalAlignment="Top"
RenderTransformOrigin="0.296,-1.239">
<TextBlock.DataContext>
<local:ViewModel/>
</TextBlock.DataContext>
</TextBlock>
</Grid>
</Window>
Actually I don't know how I can implement this as my "apiResult" is always the initial value "null", and I want it to get the result from the METHOD GetDataFromApi
How can I get all this working in the MVVM, and with some interface implemented.
I have looked at various tutorials but cant seem to grasp it as they all have something lacking from the beginning or I don't quite understand the logic...
Have also pushed this to GIT: https://github.com/lklancir/WPFexample/tree/master/WPFexample/WPFexample
Hope you can just point me in the right direction...

It worked for me, if GetDataFromApi is actually called. Add this code to DataModel.cs and the gui shows "SOME RESULT FROM WEB API"
public DataModel()
{
Task.Factory.StartNew( () => this.GetDataFromApi() );
}
But this is a timing issue. If you add a sleep to the task it will no longer work, because nothing propagates changes of the properties. You should implement INotifyPropertyChanged or use DependencyProperties.

Related

I can not get MVVM to work

I am desperately trying to implement MVVM and for some reason it is not working. I am using MVVM light on a Windows 8.1 Store App.
What am I doing wrong? I followed three tutorials by now and nothing seems to work..
I retrieve the Data from a Webservice and that part 100% works just fine. The ObservableCollection contains data.
The rest of my code looks like this:
ViewModelLocator:
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
// Create design time view services and models
SimpleIoc.Default.Register<IDesignTimeWeatherServiceLayer, DesignTimeWeatherServiceLayer>();
}
else
{
// Create run time view services and models
SimpleIoc.Default.Register<IWeatherServiceLayer, WeatherServiceLayer>();
}
SimpleIoc.Default.Register<WeatherViewModel>();
}
public WeatherViewModel Weather
{
get
{
return ServiceLocator.Current.GetInstance<WeatherViewModel>();
}
}
ViewModel:
public class WeatherViewModel : ViewModelBase
{
WeatherServiceLayer serviceLayer = new WeatherServiceLayer();
public async void GetAllWeatherData()
{
WeatherData = await serviceLayer.GetAllWeatherAsync();
}
private ObservableCollection<Weather> weatherData;
public ObservableCollection<Weather> WeatherData { get { return weatherData; } set { weatherData = value; RaisePropertyChanged("WeatherData"); } }
}
Code Behind:
public MainPage()
{
this.InitializeComponent();
WeatherViewModel vm = new WeatherViewModel();
vm.GetAllWeatherData();
}
View:
...
DataContext="{Binding Weather, Source={StaticResource Locator}}">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<GridView ItemTemplate="{StaticResource WeatherItemTemplate}" ItemsSource="{Binding Weather.WeatherData, Source={StaticResource Locator}}"/>
</Grid>
DataTemplate:
<DataTemplate x:Key="WeatherItemTemplate">
<StackPanel>
<TextBlock Text="{Binding Temperature}" Height="60" Margin="15,0,15,0"/>
<TextBlock Text="{Binding WeekDay}" Margin="15,0,15,10"/>
</StackPanel>
</DataTemplate>
I'm not sure but there are a couple things that seem suspect to me. Initially you set your DataContext property. Are your sure your DataContext is what you're expecting after this assignment? You can set a breakpoint in your view's constructor after you call InitializeComponent to check.
Further in your ItemsSource binding you're saying "Weather.WeatherData" sourced by your locator. This seems redundant and maybe wrong. Maybe just try "WeatherData" and remove the Source specification. If your DataContext is a Weather, then that will be the object used for all bindings in that xaml file.

DesignInstance does not work in VS2013

Visual Studio does not show design time data with DesignInstance attribute. I have checked DesignInstance with/without MVVM Light. I have spend a lot of time to fix the issue (checked similar queestions on StackOverflow too) but DesignInstance simply does not work.
Project:
SearchIdView.
SearchIdViewModel - real View Model.
DesignSearchIdViewModel - inherits from SearchIdViewModel and contains design time data (properties are assigned in constructor).
Environment:
VS2013 SP3
Net 4.0
MvvmLight 5.0.2.0
SearchIdView.xaml
<Window x:Class="App1.View.SearchIdView"
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:ignore="http://www.ignore.com"
xmlns:design="clr-namespace:App1.Design"
mc:Ignorable="d ignore"
DataContext="{Binding SearchId, Source={StaticResource Locator}}"
d:DataContext="{d:DesignInstance d:Type=design:DesignSearchIdViewModel,IsDesignTimeCreatable=True}"
>
<Grid>
<TextBlock Text="{Binding Test}" />
</Grid>
SearchIdViewModel.cs
Property from SearchIdViewModel
public const string TestPropertyName = "Test";
private string _test;
public string Test
{
get
{
return _test;
}
set
{
Set(TestPropertyName, ref _test, value);
}
}
Do you have any idea why DesignInstance does not work in this case?
Workaround
remove d:DataContext from view
add interface ISearchIdViewModel (it is empty)
SearchIdViewModel inherits from ISearchIdViewModel
change ViewModelLocator (below)
ViewModelLocator.cs
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<ISearchIdViewModel,Design.DesignSearchIdViewModel>();
}
else
{
SimpleIoc.Default.Register<ISearchIdViewModel, SearchIdViewModel>();
}
}
public SearchIdViewModel SearchId
{
get { return (SearchIdViewModel) ServiceLocator.Current.GetInstance<ISearchIdViewModel>(); }
}
}
Your d:DesignInstance declaration is malformed. You specify the property name d:Type instead of Type, so the property is not assigned correctly. Either replace d:Type with Type, or leave the property name off entirely and let it be inferred as the default property.
d:DataContext="{d:DesignInstance d:Type=design:DesignSearchIdViewModel,
IsDesignTimeCreatable=True}"
Should become:
d:DataContext="{d:DesignInstance Type=design:DesignSearchIdViewModel,
IsDesignTimeCreatable=True}"
Or, alternatively:
d:DataContext="{d:DesignInstance design:DesignSearchIdViewModel,
IsDesignTimeCreatable=True}"
(line wrapping added for readability)
Another cause that might make d:DesignInstance not to work is that all data must be properties not just public variables of mock class! I know that it was not your problem, but it should be checked if for someone it does not work.
Will not work with:
public class MockFile
{
public FilePRJO FilePRJO = new FilePRJO();
}
But it will work with:
public class MockFile
{
public FilePRJO _filePRJO = new FilePRJO();
public FilePRJO FilePRJO
{
get
{
return _filePRJO;
}
}
}

Binding to properties in both the ViewModel and CodeBehind

I have what I'm sure is a ridiculously ignorant question, but I'm asking it anyways because I've searched and searched and either don't understand the solutions I'm seeing or not finding exactly the answer I seek.
I have an MVVM application. My XAML is setup with the DataContext set to the VM where the data items on the screen are populated from the VM's properties. My CodeBehind doesn't fiddle with the data, only things relating to the screen.
What I want to do now is bind certain UI elements to properties in the foo.xaml.cs (CodeBehind) file. For example, I want to specify FontSize's bound to properties in the CB so that in the WindowInitialized handler in the CB, it can detect screen sizes and change one variable to which all the screen items' FontSize= are bound.
I can solve this the wrong way by creating a public property in my VM and then "inject" the value from the CB into the VM. I know that will work, but it's a roundabout way to get the behavior I want, it's not at all straightforward, and I feel confident it's the wrong way to proceed.
I searched around and have tried things like:
FontSize="{Binding RelativeSource={RelativeSource Self},Path="MyFontSize"
(where "MyFontSize" is a public int property) and a variety of other examples I found, but none have worked.
So specifically, if my CodeBehind class is called NameChangeSetupMainWindow and that's where the "MyFontSize" property lives,
public partial class NameChangeSetupMainWindow : Window
{
private int m_fontSize = 14;
public int MyFontSize
{
get { return m_fontSize; }
set
{
if (m_fontSize != value))
{
m_fontSize = (value > 0) ? value : 10;
}
}
}
...
... rest of the class...
...
}
and the VM is called NameChangeSetupViewModel and that's where the "real" data lives and the DataContext points ala:
<Window.DataContext>
<local:NameChangeSetupViewModel/>
</Window.DataContext>
what is the syntax in XAML to bind just those UI items (tooltips related to the UI, font sizes, etc) to variables in the CodeBehind instead of housing them in the VM?
Thanks in advance for any guidance you can supply.
You can use RelativeSource AncestorType to bind to properties of the view itself:
<TextBlock FontSize="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=MyFontSize}" />
Using ElementName should work as well:
<Window x:Name="window">
<TextBlock FontSize="{Binding ElementName=window,Path=MyFontSize}" />
</Window>
Edit
Here is an example that I've confirmed working:
XAML
<Window x:Class="WpfAbc.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
ToolTip="{Binding RelativeSource={RelativeSource Self},Path=MyToolTip}"
>
<Grid>
<TextBlock Text="hello world" FontSize="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=MyFontSize}" />
</Grid>
</Window>
Code Behind
public partial class MainWindow : Window
{
private int m_fontSize = 20;
public int MyFontSize
{
get { return m_fontSize; }
set
{
if (m_fontSize != value)
{
m_fontSize = (value > 0) ? value : 10;
}
}
}
public string MyToolTip
{
get { return "hello world"; }
}
public MainWindow()
{
InitializeComponent();
}
}
Articles on this topic:
The RelativeSource markup extension
XAML binding declarations
Related background:
"Namescopes" in XAML (when binding to a source using "ElementName", the source element must be in the same namescope)
Visual tree vs logical tree in XAML (elements not in the visual tree, like Popup and ContextMenu, do not inherit DataContext. Binding from these elements requires a workaround like the "data context spy" technique.)

Creating a custom image class in WPF following MVVM pattern

Im just starting out with MVVM and at the moment still find alot of things confusing.
So I am trying to keep things as simple as I can at the moment.
I am trying to write code for a custom image which later will be able to be placed on a canvas control by a user at runtime. I'm trying to use MVVM so that I will be able to save and reload the content on a canvas.
I have created a model class called CustomImage with the following code:
namespace StoryboardToolMvvm
{
public class CustomImage
{
public Uri imageLocation { get; set; }
public BitmapImage bitmapImage { get; set; }
}
}
I have a modelview class as follows:
namespace StoryboardToolMvvm
{
class CustomImageViewModel : ViewModelBase
{
private CustomImage _customImage;
private ObservableCollection<CustomImage> _customImages;
private ICommand _SubmitCommand;
public CustomImage CustomImage
{
get { return _customImage; }
set
{
_customImage = value;
NotifyPropertyChanged("CustomImage");
}
}
public ObservableCollection<CustomImage> CustomImages
{
get { return _customImages; }
set
{
_customImages = value;
NotifyPropertyChanged("CustomImages");
}
}
public ICommand SubmitCommand
{
get
{
if (_SubmitCommand == null)
{
_SubmitCommand = new RelayCommand(param => this.Submit(), null);
}
return _SubmitCommand;
}
}
public CustomImageViewModel()
{
CustomImage = new CustomImage();
CustomImages = new ObservableCollection<CustomImage>();
CustomImages.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(CustomImages_CollectionChanged);
}
private void CustomImages_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("CustomImages");
}
private void Submit()
{
CustomImage.imageLocation = new Uri(#"H:\My Pictures\whale.png");
CustomImage.bitmapImage = new BitmapImage(CustomImage.imageLocation);
CustomImages.Add(CustomImage);
CustomImage = new CustomImage();
}
}
}
And a view class:
<UserControl x:Class="StoryboardToolMvvm.CustomImageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewmodel="clr-namespace:StoryboardToolMvvm"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<viewmodel:CustomImageViewModel x:Key="CustomImageViewModel"/>
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource CustomImageViewModel}}">
<Image Source="{Binding CustomImage.bitmapImage, Mode=TwoWay}" Width="150" Height="150" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="75,50,0,0" />
<Button Content="Submit" Command="{Binding SubmitCommand}" Width="100" Height="50" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,20" />
</Grid>
</UserControl>
I add this view to my MainWindow.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StoryboardToolMvvm" x:Class="StoryboardToolMvvm.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:CustomImageView HorizontalAlignment="Left" Height="100" Margin="181,110,0,0" VerticalAlignment="Top" Width="100"/>
</Grid>
</Window>
I am very unsure as to whether I am on the right lines here with a MVVM pattern so any comments would be much appreciated. Also when Submit is pressed I would have expected my image to load but this does not happen can anyone advise as to why?
Many Thanks in advance..
As far as my understanding of MVVM and your question goes, I have one main comment about your code.
I think your CustomImage is actually both Model and ViewModel layer, and you should split it in two :
the Model, which would contain the path itself ;
the ViewModel, which contain the BitmapImage and initialize it from the Model and constructing time.
The path is the mere data used for saving, and it fits the Model, whereas the BitmapImage is how the data is shown and should be constructed in the ViewModel.
One advantage is that now, your BitmapImage gets its own NotifyPropertyChanged call at setting time, and you won't have anymore problem or a View part directly bound to the Model.
As for your CustomImageViewModel, this looks like more of a MainViewModel-ish thing. You can still use this to store the ViewModels.

DataTemplate for MVVM: where does it go?

I'm working on a "Modern UI" application so the syntax is a bit new for me and I just can't seem to get my bindings to work properly.
My desire is to have a ViewModel first design so that on my apps pages I can just do things like have a ListView and add a UserViewModel as a child, and have the DataTemplate be found automatically to create a UserView and bind to the supplied UserViewModel.
I do something similar for a different app written for Win 7 desktop and it just works but for the life of me I can't figure out why it doesn't work here. I just get in my ListView "UserViewModel" as text (no UserControl created).
The only other difference here is it is the first time I'm using async functions since it pretty much is forced on you for Win 8 development, and that is the methods I get from the WCF service I'm pulling my data from.
Here's an example of my view model:
public class UserViewModel
{
private UserDTO _user { get; set; }
public UserViewModel(UserDTO user)
{
_user = user;
}
public UserViewModel(int userId)
{
SetUser(userId);
}
private async void SetUser(int userId)
{
ServiceClient proxy = new ServiceClient();
UserDTO referencedUser = await proxy.GetUserAsync(userId);
}
public string FirstName
{
get
{
return _user.FirstName;
}
}
public string LastName
{
get
{
return _user.LastName;
}
}
public string Email
{
get
{
return _user.email;
}
}
}
The view is supposed to be all XAML and glued together in the application resources as follows:
<UserControl x:Class="TaskClient.Views.UserView" ...
xmlns:root="using:TaskClient"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="30"
d:DesignWidth="200">
<StackPanel Orientation="Horizontal" Margin="5, 0, 0 ,0" DataContext="{Binding}">
<TextBlock x:Name="FirstNameLabel" Text="{Binding FirstName}"/>
<TextBlock x:Name="LastNameLabel" Text="{Binding LastName}"/>
<TextBlock x:Name="EmailLabel" Text="{Binding Email}"/>
</StackPanel>
</UserControl>
and :
<Application x:Class="TaskClient.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TaskClient"
xmlns:localData="using:TaskClient.Data"
xmlns:vm="using:ViewModels"
xmlns:vw="using:TaskClient.Views">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--
Styles that define common aspects of the platform look and feel
Required by Visual Studio project and item templates
-->
<ResourceDictionary Source="Common/StandardStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!-- Application-specific resources -->
<x:String x:Key="AppName">TaskClient</x:String>
<DataTemplate x:Key="vm:UserViewModel">
<vw:UserView />
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
I've tried searching for an hour or so now through various examples (eg. http://joshsmithonwpf.wordpress.com/a-guided-tour-of-wpf/) and haven't been able to find an example that works in my case.
Any idea what I'm doing wrong?
Could be a typo but you don't seem to update the "_user" field when you fetch a user via WCF service. You probably need to change this:
private async void SetUser(int userId)
{
ServiceClient proxy = new ServiceClient();
UserDTO referencedUser = await proxy.GetUserAsync(userId);
}
To this:
private async void SetUser(int userId)
{
ServiceClient proxy = new ServiceClient();
_user = await proxy.GetUserAsync(userId);
}
Also I don't see your ViewModel class implementing INotifyPropertyChange interface which is the key to WPF databinding. Once that's done and you have loaded a user, you need to notify WPF about properties being updated:
private async void SetUser(int userId)
{
ServiceClient proxy = new ServiceClient();
_user = await proxy.GetUserAsync(userId);
NotifyOfPropertyChange();
}
private void NotifyOfPropertyChange()
{
NotifyChanged("FirstName"); //This would raise PropertyChanged event.
NotifyChanged("LastName");
NotifyChanged("Email");
}
I just get in my ListView "UserViewModel" as text (no UserControl created)
Your DataTemplate needs be defined with the DataType property and no x:Key property, for the DataTemplate to get applied implicitly
<DataTemplate DataType="{x:Type vm:UserViewModel}">
<vw:UserView />
</DataTemplate>
A DataTemplate with a DataType specified but no x:Key is an Implicit DataTemplate, meaning it will be used implicitly anytime WPF needs to draw an object of the specified data type.
A DataTemplate with an x:Key property needs to actually be specified in your code by key, such as
<ListView ItemTemplate="{StaticResource MyKey}" ... />
Also, I'm not sure if the title of your question ("DataContext for MVVM: where does it go?") is a typo or not since your question body doesn't appear to be asking about the DataContext, however I have an beginners article on my blog explaining the DataContext that you may be interested in if you're struggling to understand the DataContext

Categories

Resources