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!!
Related
I have delved into the magic and mystery of WPF and Binding. It was going OK then I hit a brick wall and need to ask those much cleverer than me for help please.
I cut this back to a simple app removing all the other items in my code. The UI has a text box and a label. When the text in the textbox changes then I want to update the label. Somewhere I am missing a link and I guess it is the binding as I never seem to get into the set. Here is the code
Mainwindow.xaml.cs
using System.ComponentModel;
using System.Windows;
namespace Databinding3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string myBindedVal = "....";
public MainWindow()
{
InitializeComponent();
}
//Create properties for our variable _myBindedVal
public string MyBindedVal
{
get => myBindedVal;
set
{
NotifyPropertyChanged(nameof(MyBindedVal));
myBindedVal = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (propertyName != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Mainwindow.xml
<Window x:Class="Databinding3.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:Databinding3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBox x:Name="txtbx_name" Text="Textbox" HorizontalAlignment="Center" Height="57" TextWrapping="Wrap" VerticalAlignment="Center" Width="594"/>
<Label Content="{Binding MyBindedVal, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" HorizontalAlignment="Center" Height="44" Grid.Row="1" VerticalAlignment="Center" Width="594"/>
</Grid>
</Window>
Thanks for your help
You did not bind the Text property of the TextBox. It should look like shown below, where the UpdateSourceTrigger ensures that the source property is updated immediately when you type into the TextBox.
<TextBox Text="{Binding MyBoundVal, UpdateSourceTrigger=PropertyChanged}" .../>
The above Binding does not explicitly specify a source object, and therefore uses the Window's DataContext as source. Set the DataContext like this:
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
The Label Binding would then just be
<Label Content="{Binding MyBoundVal}" .../>
Be aware that you would typically use a TextBlock, not a Label, to show text:
<TextBlock Text="{Binding MyBoundVal}" .../>
The execution order in the property setter is also important. Assign the backing field value before firing the PropertyChanged event.
public string MyBoundVal
{
get => myBoundVal;
set
{
myBoundVal = value;
NotifyPropertyChanged(nameof(MyBoundVal));
}
}
Finally, the NotifyPropertyChanged method should look like shown below. Testing the propertyName argument is pointless, but you should test the PropertyChanged event for null, usually by using the null-propagation operator ?.:
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
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 making a basic program where a label updates when the user types in a text box. i'm trying to use data binding and INotifyPropertyChanged to work this out, so i don't want any workarounds. i used 2 buttons so i can actually see if they updated. here's my main class
namespace TestStringChangeFromAnotherClass
public partial class MainWindow : Window
{
textClass someTextClass = new textClass();
public MainWindow()
{
InitializeComponent();
}
public string someString1;
public string someString2;
private void btn1_Click(object sender, RoutedEventArgs e)
{
someTextClass.Text1 = tbx1.Text;
}
private void btn2_Click(object sender, RoutedEventArgs e)
{
someTextClass.Text2 = tbx1.Text;
}
}
here's the wpf for it
<Window x:Class="TestStringChangeFromAnotherClass.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button x:Name="btn1" Content="Button" HorizontalAlignment="Left" Height="36" Margin="29,246,0,0" VerticalAlignment="Top" Width="108" Click="btn1_Click"/>
<Button x:Name="btn2" Content="Button" HorizontalAlignment="Left" Height="36" Margin="227,246,0,0" VerticalAlignment="Top" Width="124" Click="btn2_Click"/>
<Label x:Name="lbl1" Content="{Binding textClass.Text1}" HorizontalAlignment="Left" Height="37" Margin="74,32,0,0" VerticalAlignment="Top" Width="153"/>
<Label x:Name="lbl2" Content="{Binding textClass.Text2, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="38" Margin="74,90,0,0" VerticalAlignment="Top" Width="153"/>
<TextBox x:Name="tbx1" HorizontalAlignment="Left" Height="37" Margin="290,32,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="190"/>
</Grid>
as you can see, i've tried using UpdateSourceTrigger. i've also tried to use "someTestClass.Text1" instead of textClass.Test1, because that's how i defined it in the MainWindow. Here's my textClass
namespace TestStringChangeFromAnotherClass
public class textClass:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string text1;
public string Text1
{
get { return text1; }
set
{
text1 = value;
NotifyPropertyChanged("Text1");
}
}
private string text2;
public string Text2
{
get { return text2; }
set
{
text2 = value;
NotifyPropertyChanged("Text2");
}
}
protected void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
i can't figure out how to get wpf to look for the Test1 or Test2 strings in the separate class and update them when the strings change. i have a feeling the problem lies within DataContext, but i can't figure it out. i'd also rather not use DataContext within c#, only in WPF
UPDATE:
when i debug this, when it gets to NotifyPropertyChanged, PropertyChanged is evaluated as null. could that be the problem?
You bind DataContext to your Window which, as far as I can see, doesn't have textClass property. It has someTextClass field of textClass type. In order for your code to work your can change someTextClass to public property:
public textClass someTextClass { get; private set; }
initialize it in constructor:
public MainWindow()
{
someTextClass = new textClass();
InitializeComponent();
}
and then change binding to point to someTextClass property
<Label x:Name="lbl1" Content="{Binding someTextClass.Text1}" .../>
<Label x:Name="lbl2" Content="{Binding someTextClass.Text2}" .../>
You are binding to the MainWindow class itself as your DataContext, and trying to access the property called someTextClass that has the properties you want to bind to.
You are running into two problems:
1) Your XAML is trying to reference the desired object by it's type, not it's name. Not going to work. Your binding expressions should look like {Binding someTextClass.Text1} (note the difference in the first part of the path expression).
2) You can only bind to public things. Your field is not defined as public, and therefore is private. Even though the XAML should logically "be able to see" the property, as it's the same class, DataBinding will only work on public properties.
3) EDIT: You must also make this a property. WPF will not bind to fields.
In general, using Snoop will help diagnose silent binding errors.
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.
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.