I'm new to Silverlight and the concept of data-binding and I still fail on resolving my problem. I haven't manage to find a solution after few days of research.
Here is my problem :
I correctly bind a String property to the text of my TextBlock as you can see below :
MainPage.xaml
<Grid Background="Blue" DataContext="{StaticResource WP8Displayable}">
<TextBlock x:Name="tbCanvasTitle" TextWrapping="Wrap" Text="{Binding titleDisplayable}" FontWeight="Bold" HorizontalAlignment="Center"/>
</Grid>
WP8Displayable.cs
public class WP8Displayable : IDisplayable, INotifyPropertyChanged
{
public String title { get; set; }
#region INotifyPropertyChanged Members
public string titleDisplayable
{
get
{
return title;
}
set
{
if (title != value)
{
title = value;
NotifyPropertyChanged("titleDisplayable");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify the page that a data context property changed
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public void setTitle(String s)
{
this.title = s;
NotifyPropertyChanged("titleDisplayable");
}
}
I have a thread in my MainPage.xaml.cs that can instantiate one or more instance of WP8Displayable class. When one of the instance call setTitle(String s) the text in my TextBlock does not update, it seems that my DataContext is not set up properly.
EDIT :
My thread is launched in the MainPage.xaml.cs in the MainPage_Loaded(object sender, RoutedEventArgs e) method and does something like this :
var instanceWP8Displayable = new WP8Displayable();
//tbCanvasTitle.DataContext = instanceWP8Displayable; HERE IS WHAT I WOULD LIKE TO DO ON XAML
instanceWP8Displayable.setTitle("my Title");
EDIT 2 : App.xaml
<Application
x:Class="AMS.App"
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:windows="clr-namespace:System.Windows;assembly=System.Windows"
xmlns:ioC="clr-namespace:AMS.Controller">
<!--Application Resources-->
<Application.Resources>
<windows:ResourceDictionary>
<local:LocalizedStrings xmlns:local="clr-namespace:AMS" x:Key="LocalizedStrings"/>
<ioC:Locator x:Key="Locator" x:Name="Locator" />
</windows:ResourceDictionary>
</Application.Resources>
MainPage.xaml
<Grid x:Name="LayoutRoot" Background="White">
<Grid Background="Blue" DataContext="{Binding Source={StaticResource Locator}, Path=WP8Displayable}">
<TextBlock x:Name="tbCanvasTitle" TextWrapping="Wrap" Text="{Binding titleDisplayable}" FontWeight="Bold" HorizontalAlignment="Center"/>
</Grid>
<Grid.DataContext>
<local:WP8Displayable />
</Grid.DataContext>
</Grid>
How can I dynamically set up the DataContext in that case ? And Is it possible to link more than one instance to the same object ?
If anyone as a clue or feels that my question is not clear enough don't hesitate to tell me.
Thank you.
I would recommend you check out MVVMLight which will help you eliminate a lot of boiler plate code you have to write e.g. INotifyPropertyChanged. Further it provides you with an IoC Container (Inversion of Control) that is most commonly used for the task you are trying to perform. You can implement a simple version of one on your own (see bellow).
You can set the DataContext in XAML but you will need a class that provides the Object, so for instance you could write a class like this (I'm assuming you add it directly to the project and not in a subfolder):
public class Locator
{
public WP8Displayable WP8Displayable
{
get { return new WP8Displayable(); }
}
}
Next you will have to register the Locator class in the App.xaml so you can reference it from within your view:
<Application
...
xmlns:windows="clr-namespace:System.Windows;assembly=System.Windows"
xmlns:ioC="clr-namespace:YOURAPPNAME">
<!--Application Resources-->
<Application.Resources>
<windows:ResourceDictionary>
<local:LocalizedStrings xmlns:local="clr-namespace:AMS" x:Key="LocalizedStrings"/>
<ioC:Locator x:Key="Locator" x:Name="Locator" />
</windows:ResourceDictionary>
</Application.Resources>
...
</Application>
Now we can set the DataContext in XAML:
<Grid Background="Blue" DataContext="{Binding Source={StaticResource Locator}, Path=WP8Displayable}">
<TextBlock x:Name="tbCanvasTitle" TextWrapping="Wrap" Text="{Binding titleDisplayable}" FontWeight="Bold" HorizontalAlignment="Center"/>
</Grid>
HTH
Your way for declaration property is wrong.
Plz Declare property as below;
#region INotifyPropertyChanged Members
public string _titleDisplayable;
public string titleDisplayable
{
get
{
return _titleDisplayable;
}
set
{
if (_titleDisplayable != value)
{
_titleDisplayable = value;
NotifyPropertyChanged("titleDisplayable");
}
}
}
and In class methods plz write below code:
public void setTitle(string s)
{
this.titleDisplayable = s;
}
Plz changes ur code as per above code.
Thanks ,
Hitesh.
Related
Hi everyone i am a newbie in UWP development, i have searched a lot on the net but I have not found the right way to achieve my goal, what i would like to do is update the ui of my MainPage through the use of a class belonging to UiUpdate class. Here's what I'd like to get, this is my MainPage:
namespace Test_App
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
}
}
This is the relative xaml associated with my MainPage:
<Page
x:Class="Test_App.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Test_App"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<controls:DockPanel>
<controls:DockPanel Visibility="Visible" HorizontalAlignment="Left" >
<Grid>
<TextBlock x:Name="text_one" HorizontalAlignment="Right" Margin="0,50,46,0" VerticalAlignment="Center" FontWeight="Bold" />
<TextBlock x:Name="text_two" HorizontalAlignment="Left" VerticalAlignment="Bottom" FontWeight="Medium" />
</Grid>
</controls:DockPanel>
</Grid>
</Page>
Now through the UiUpdate class, I would like to update my TextBlocks or any other element of my UI, I found something like this on the net:
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync (CoreDispatcherPriority.Normal, () =>
{
// Update texboxt
});
But i can't access in any way in the UiUpdate class the texbox elements of the MainPage i can't do MainPage.text_one .., also i can't find good documentation for what concerns patterns to be applied or other for the realization of my UWP app
How to update item xaml from another class in UWP
For explain this question, you may need to refer UWP mvvm design, and you mentioned UiUpdate most like ViewModel.
For example
<Page.DataContext>
<local:UiUpdate x:Name="ViewModel" />
</Page.DataContext>
<Grid>
<TextBlock
x:Name="text_one"
Margin="0,50,46,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding TextBlockText}" />
<Button VerticalAlignment="Bottom" Click="Button_Click">Update</Button>
</Grid>
Code behind
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private Random random = new Random();
private void Button_Click(object sender, RoutedEventArgs e)
{
ViewModel.TextBlockText = $"Text-----{random.Next(15)}";
}
}
public class UiUpdate : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _textBlcokText;
public string TextBlockText
{
get
{
return _textBlcokText;
}
set
{
_textBlcokText = value;
OnPropertyChanged();
}
}
}
MainWindow.xaml
<Window x:Class="SDT.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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
xmlns:viewModels="clr-namespace:SDT.ViewModels"
Height="500" Width="700" WindowStyle="None" AllowsTransparency="False" ResizeMode="NoResize" Background="#FF2C2C2C"
TextElement.Foreground="{DynamicResource MaterialDesignBody}" TextElement.FontWeight="SemiBold">
<Window.DataContext>
<viewModels:UserViewModel />
</Window.DataContext>
<Grid>
<TextBox HorizontalAlignment="Left" Height="23" Margin="308,90,0,0" TextWrapping="Wrap" Text = "{Binding Login}" VerticalAlignment="Top" Width="120"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="152,200,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Text="{Binding Path=FirstName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<Button Content="Submit" Command="{Binding SubmitLoginDataCommand}" HorizontalAlignment="Left" Margin="567,259,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</Window>
MainWindows.cs
public partial class MainWindow : Window
{
UserViewModel userViewModel = new UserViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = userViewModel;
}
}
UserViewmodel
public class UserViewModel : INotifyPropertyChanged
{
private UserService userService = new UserService();
public string _firstName;
public string Login { get; set; }
public void SubmitLoginData(object loginData)
{
userService.CheckUserExist(Login);
}
public ICommand SubmitLoginDataCommand => new RelayCommand(SubmitLoginData, param => true);
public string FirstName
{
get { return _firstName; }
set
{
if (_firstName != value)
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Hello.
What is wrong with FirstName binding?
Textbox shows nothing.
public string FirstName{} - FirstName here have value in debugging.
I tried without Window.DataContext and only with Text="{Binding FirstName}" but without success.
Login binding working fine.
You need to remove from MainWindow.xaml this part:
<Window.DataContext>
<viewModels:UserViewModel />
</Window.DataContext>
It becouse you have Twice DataContext,
In xaml and in cs so it is not know from where take the data.
I wanted to post a second answer to make a suggestion that I think you'll really like. We can have your OnPropertyChanged event automatically name the property, allowing you to just write "OnPropertyChanged()" to trigger the UI update.
To do this, we're going to use a property called "Caller Member Name" - which does what you'd think - provides the name of the object or property that's called the code!
To use this, we need to add a using statement to the top of your UserViewModel class:
using System.Runtime.CompilerServices;
Then, we will modify your OnPropertyChanged event to use the 'caller member name' unless you specify a specific name. It should look like this now:
private void OnPropertyChanged([CallerMemberName] String name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
Now - we will update your property to use the simplified method:
public string FirstName
{
get { return _firstName; }
set
{
if (_firstName != value)
{
_firstName = value;
OnPropertyChanged();
}
}
}
Lastly - I've recently learned an alternative way for get/set's that i prefer. What you're doing is completely OK and there is no need to change it, but i'd suggest trying it to see how you like it :)
public string FirstName
{
get => _firstName;
set
{
if (_firstName == value) return;
_firstName = value;
OnPropertyChanged();
}
}
Reasons: I find it quicker to press == instead of !=, less brackets. If first name equals value it will simply return (exit). If not, it skips that return! I love that!
Let's test your binding! Let's add a text block to your form, and bind the FirstName property to it. Whatever you enter in the Textbox should be displayed in the textblock if your binding is working correctly.
Your MainWindow.xaml should look something like this:
<Window x:Class="SDT.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:viewModels="clr-namespace:Junk.cats"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBox HorizontalAlignment="Left" Height="23" Margin="308,90,0,0" TextWrapping="Wrap" Text = "{Binding Login}" VerticalAlignment="Top" Width="120"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="152,200,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Text="{Binding Path=FirstName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<Button Content="Submit" Command="{Binding SubmitLoginDataCommand}" HorizontalAlignment="Left" Margin="567,259,0,0" VerticalAlignment="Top" Width="75"/>
<TextBlock HorizontalAlignment="Left" Height="32" Margin="140,247,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="346">
<Run Text="First Name: "/>
<Run Text="{Binding Path=FirstName}"/>
</TextBlock>
</Grid>
I expect that this test will work, and you're going to see that you're not having an issue with your get/set properties and UI updates. I believe your issue is now with the 'instance' (copy) of the UserViewModel.
Let's pretend we're working with a printed document. When you use the = new UserService(); assignment, you're printing a fresh copy of our document. If we print a new document and give it to MainWindow.cs (Let's call it "Bob"), AND you then print a new copy in your userService code (Let's call this "Frank") - these are two independent instances / copies of the document.
We need to make this object once, and tell "Bob" and "Frank" to work with the same copy of the object. Don't worry, this is easier than you think, and you'll start getting used to it as you use it.
I'm going to use some STATIC fields to simplify your troubleshooting - you do not need to create a static instance to make this work, but you do need to make sure your instance of the shared class is available to whoever needs it.
Step 1 - Create a new class, let's call it 'Views'.
Step 2 - Make the class public static
Step 3 - Create a Public static userViewModel here:
public static class views
{
public static UserViewModel userViewModel = new UserViewModel();
}
Now - Let's change your MainWindow.cs to use the shared instance of this class:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = views.userViewModel;
}
}
The last thing you need to do - Make your external function work with the same copy of the 'userViewModel'! I don't have that code from you, so I'm pretending your function is called 'YourFunctioNToChangeTheName', and it's located in your 'UserService' class:
public class UserService
{
public void YourFunctionToChangeTheName()
{
views.userViewModel.FirstName = "FRANK";
}
}
The key thing to spot here is that you're not creating a new "UserViewModel" - you're re-using the same instance that the MainWindow.cs is bound to - so the UI is getting a 'property changed notification' now!
Remember, the UserViewModel (class) itself isn't static, we've created a shared / static instance of it that can be accessed from anywhere in your program. I suggested this approach so that you can learn the basics of an instance :)
Good luck!!
I have an app that uses MVVM pattern and implements INotifyPropertyChanged but it's not working. Basically when I choose a Wine from a list and click 'open', another usercontrol should load with all the details filled in.
I followed along with a pluralsight course and have tried to adapt it and create something of my own. I have gone through the source code of the pluralsight course and stack-overflow questions for many hours and just can't see what I'm missing. Going crazy here.. :(
After alot of searching, I know my databinding is working, because I can force an update to the target like so:
txtWijnNaam.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
I also tried adding the INotifyPropertyChanged to my Wine model class as well as the viewmodel but this didn't work. And it's also not used like this in the working source code of the pluralsight course so shouldn't be necessary.
The viewmodel I'm using:
class WineDetailViewModel : ViewModelBase
{
private readonly string _baseUri = "https://localhost/api/wines";
private IEventAggregator _eventAggregator;
public WineDetailViewModel()
{
_eventAggregator = EventAggregatorSingleton.Instance;
_eventAggregator.GetEvent<OpenWineDetailViewEvent>().Subscribe(OnOpenWineDetailView);
}
private void OnOpenWineDetailView(int wineId)
{
_wine = ApiHelper.GetApiResult<Wine>($"{ _baseUri}/{wineId}");
}
private Wine _wine;
public Wine WineFull
{
get { return _wine; }
set
{
_wine = value;
OnPropertyChanged();
}
}
}
And the base class it inherits from, implementing the INotifyPropertyChanged interface:
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Part of my Xaml file, I tried setting updateSourceTrigger like I saw on some answers here but this didn't help either:
<UserControl x:Class="WineGUI.View.WineDetailView"
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:local="clr-namespace:WineGUI.View"
xmlns:ViewModels="clr-namespace:WineGUI.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<ViewModels:WineDetailViewModel/>
</UserControl.DataContext>
<StackPanel>
<Grid Margin="5">
<TextBlock Text="Naam :" VerticalAlignment="Center"/>
<TextBox Grid.Column="1" Name="txtWijnNaam" Margin="5" Text="{Binding WineFull.Name, Mode=TwoWay}"/>
<TextBlock Grid.Row="1" Text="Jaar :" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1" Name="txtWijnYear" Margin="5" Text="{Binding WineFull.Year, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Grid.Row="2" Text="Prijs :" VerticalAlignment="Center"/>
I do notice the OnPropertyChanged() method of my WineFull property is never called but I don't get why. The pluralsight course has an identical set-up (except for some naming of course) and it works just fine..
Any help would be much appreciated. If I need to add any more info or code, please let me know.
You are setting the value of the backing field not the property so the OnPropertyChanged method is never called.
You can call OnPropertyChanged for the WineFull property in the OnOpenWineDetailView method:
private void OnOpenWineDetailView(int wineId)
{
_wine = ApiHelper.GetApiResult<Wine>($"{ _baseUri}/{wineId}");
OnPropertyChanged(nameof(WineFull));
}
Or you can use the property:
private void OnOpenWineDetailView(int wineId)
{
WineFull= ApiHelper.GetApiResult<Wine>($"{ _baseUri}/{wineId}");
}
I'm trying to implement the MVVM, so i dont know the following is correct.
It seems that ViewModel is some kind of model of the view, so associations in view shall be shown in ViewModel, in that case there shall be some associations between ViewModels. so by creating some templates for ViewModel Types, it seems the application can work, here is some example code:
ViewModels:
public class SomeVm : INotifyPropertyChanged
{
public SomeVm()
{
SomeOtherVm = new SomeOtherVm();
}
public INotifyPropertyChanged SomeOtherVm { set; get; }
private int _a;
public int A
{
set {
_a= value;
B = value;
}
get { return _a; }
}
private int _b;
public int B
{
set
{
_b = value;
OnPropertyChanged("B");
}
get { return _b; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class SomeOtherVm : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _c;
public int C
{
set
{
_c = value;
D = value;
}
get { return _c; }
}
private int _d;
public int D
{
set
{
_d = value;
OnPropertyChanged("D");
}
get { return _d; }
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And the View:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<wpfApplication1:SomeVm x:Key="SomeVm"/>
<DataTemplate DataType="{x:Type wpfApplication1:SomeVm}">
<StackPanel d:DesignWidth="339" d:DesignHeight="54">
<TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding A}" VerticalAlignment="Stretch"/>
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding B}" VerticalAlignment="Stretch"/>
<ContentPresenter Content="{Binding SomeOtherVm}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type wpfApplication1:SomeOtherVm}">
<StackPanel d:DesignWidth="339" d:DesignHeight="54">
<TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding C}" VerticalAlignment="Stretch"/>
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding D}" VerticalAlignment="Stretch"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentPresenter Content="{DynamicResource SomeVm}" />
</Grid>
</Window>
in this way all the views can be created in some Resource Dictionaries, so the question is: is it right to use MVVM like this? And if it is, what is the drawbacks?
Usually ViewModel is supposed to be the DataContext for the whole view i.e it should be the entity incharge of providing Data to view to render itself and to listen to UI command, events and property change to interact with the Business Layer (model).
The way you implemented it is you have your VM as a resource and set it as content not DataContext for one contentpresented and for the scenerio you have mentioned it might work well. But you should set VM as the DataContext for the whole view so that all the elements in the view can bind to the properties in the VM to render their state.
In your scenerio, if you have to add one more UI element in you view apart from the ContentPresenter then again you will have to access your resource VM.
So if you set you VM instance as DataContext (like this.DataContext = new ViewModel()) and bind your contentpresenter Content to DataContext of view like Content={Binding} that will be more correct and will help you if you ever want to extend your view. Here is a nice msdn article regarding mvvm implementation http://msdn.microsoft.com/en-us/library/gg405484(v=pandp.40).aspx
Thanks
Speaking in terms of ViewModel nesting, this code looks correct at a glance. Bindings you set up in XAML are also correct.
Concerning drawbacks, I would refrain from creating wpfApplication1:SomeVm in window resources. Usually DataContext of the Window is set to an instance of a WindowViewModel, which would in turn hold a reference to SomeVm. Imagine a class like this:
public class WindowViewModel
{
public SomeVM SomeVM{get; set;}
public string Title {get; set;} //other data to bind by window
//...
}
Then, while the window is initialized, DataContext must be set to a ViewModel instance, e.g:
MainWindow.DataContext = new WindowViewModel();
In XAML you'd use bindings again:
<Grid>
<ContentPresenter Content="{Binding SomeVm}" />
</Grid>
I'd also recommend putting your implicit DataTemplates in generic.xaml dictionary, rather then within a window. This way you can reuse these templates in your whole app.
Moreover, it is far better to use a ViewModelBase class implementing common event handling, so that you don't need to reimplement INotifyPropertyChanged. Also try to avoid "magic strings" in property change notification. Be better off using lambda based approach or the new Caller Info Attributes. I'm aware of that your example code is probably simplified, but I'm commenting on it as it is.
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.