I created a simple WPF MVVM app based on Caliburn.Micro for a testing purpose and it works fine: binding, events, etc.
But all that stops once I add a Telerik control to my View (in my case I used RadMap), so once I do it the events stop working at all.
Is this possible to use Caliburn along with Telerik or it's impossible? And if possible, what should I change?
I've never had a problem with telerik and Caliburn.Micro (used it in WPF and Silverlight)
Could you show your view and viewmodel, AppBootstrapper. I think something wrong with your Binding
Here are more details regarding my code:
The ViewModel:
public class TestViewModel: PropertyChangedBase
{
private string _name = "Superman1";
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyOfPropertyChange(() => Name);
}
}
private string _psition;
public string Position
{
get { return _psition; }
set
{
_psition = value;
NotifyOfPropertyChange(() => Position);
}
}
public void NameChanged()
{
Name = "Batman";
}
public void MouseMoveHandler(object sender, MouseEventArgs e)
{
Point p = e.GetPosition((Grid)sender);
Position = string.Format("X:{0} Y:{1}", p.X, p.Y);
}
}
The Bootstrapper:
public class AppBootstrapper : BootstrapperBase
{
public AppBootstrapper()
{
Initialize();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<TestViewModel>();
}
}
And here's the View:
<Grid cal:Message.Attach="[Event MouseMove] = [Action MouseMoveHandler($source, $eventArgs)]"
Width="500" Height="500" Background="LightBlue">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBox HorizontalAlignment="Center"
VerticalAlignment="Center"
Name="TextBoxControll"
Text="{Binding Name}"
FontSize="40"
Grid.Row="0"/>
<TextBox HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Position}"
FontSize="40"
Grid.Row="1"/>
<Button Grid.Row="2" FontSize="30"
cal:Message.Attach="[Event Click] = [Action NameChanged]">
Click Me</Button>
<telerik:RadMap Grid.Row="3"/>
</Grid>
So if I want my code to work, I have to remove all the Telerik related tags and (that's even not enought!) I also have to remove the references!
The code works only after everything telerik related gets removed.
If after that I simply drag radMap to the view, without even using it, caliburn stops working: the button click event and any other event, stops working
Did you add all references for rad map control?
Telerik.Windows.Controls
Telerik.Windows.Data
Telerik.Windows.Controls.DataVisualization
I just created a project using your code and everything work fine
Binding in Caliburn works for the "out of box controls" only everything else "REQUIRES" conventions to be created since Caliburn.Micro has no clue as to the nature of those controls.
Note: You absolutely don't need x:bind for Telerik controls they work just find with for example ItemsSource="{Binding SomeItems}"
But to get them working with x:Bind you will need something along these lines....
https://github.com/vcaraulean/Caliburn.Micro.Telerik/blob/master/Silverlight/Caliburn.Micro.Telerik/TelerikConventions.cs
NB: this not as exhaustive as you would hope. RadGrid is something you will never x:Bind (pretty much a guarantee, taking this one on would be time costly, with all of the crap you would have to come up with).
Related
When I click the 'Add Some Thing' button on this simple Win Phone 8 application (built with VS 2012 Pro - its what I have), nothing happens. Why?
The repo of this example code is on bitbucket.org at:
TestItemsControlInWinPhone8App
The MainPage.xaml contains:
<phone:PhoneApplicationPage
x:Class="TestItemsControlInWinPhone8App.MainPage"
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
<TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button x:Name="AddSomeThing"
Content="Add Some Thing"
Grid.Row="0"
Click="AddSomeThing_Click"/>
<ItemsControl x:Name="LotsOfThingsItemsControl"
Grid.Row="1"
ItemsSource="{Binding Mode=OneWay}"
FontSize="{StaticResource PhoneFontSizeSmall}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="Auto" Width="Auto"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Background="Orange">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Path=Id, Mode=OneWay}"/>
<TextBlock Grid.Row="1"
Text="------------------------"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
</Grid>
</Grid>
</phone:PhoneApplicationPage>
Please note that ItemsControl ItemsSource="{Binding Path=Things} has also been tried as just plain ItemsControl ItemsSource="{Binding}".
Both have the same result; the first five Thing objects display and clicking the "Add Some Thing" button adds another Thing to LotsOfThings.
The contents of the DataTemplate being a TextBlock do, in fact, display the first 5 Thing objects.
But clicking the button does NOT update the display, which persists in displaying only the original 5 Thing objects.
The code behind (MainPage.xaml.cs) reads:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using TestItemsControlInWinPhone8App.Resources;
namespace TestItemsControlInWinPhone8App
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
this.DataContext = new LotsOfThings(5);
}
private void AddSomeThing_Click(object sender, RoutedEventArgs e)
{
LotsOfThings lot = this.DataContext as LotsOfThings;
lot.Add(new Thing());
}
}
}
Note that in the Page constructor this.DataContext = new LotsOfThings(5); works and when the Page first displays, 5 Thing objects are displayed.
To be clear, what doesn't work is that a latter call of the AddSomeThing_click() button handler will add another Thing to LotsOfThings but only the original 5 Thing objects display; nothing more, even if there are many more Thing objects present on LotsOfThings according to the debugger.
What I notice using the debugger is that whenever OnPropertyChanged(...) is called handler is null. That is clearly important but I have no idea why that is happening having at this point followed all the remedial help I can find searching the web.
Why?
Contents of Thing.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestItemsControlInWinPhone8App
{
public class Thing : INotifyPropertyChanged
{
private string _Id = Guid.NewGuid().ToString();
public string Id
{
get
{
return _Id;
}
set { }
}
#region Constructor
public Thing()
{
this.OnPropertyChanged( "Id");
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string pPropertyName)
{
System.ComponentModel.PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(pPropertyName));
}
}
#endregion
}
}
Contents of LotsOfThings.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestItemsControlInWinPhone8App
{
class LotsOfThings : INotifyPropertyChanged, IList<Thing>
{
private List<Thing> _things = new List<Thing>();
public List<Thing> Things
{
get {
return _things;
}
set { }
}
public LotsOfThings( int pNumberOfThings)
{
for( int x = 0; x < pNumberOfThings; x++){
this.Add( new Thing());
}
OnPropertyChanged("Things");
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string pName)
{
System.ComponentModel.PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(pName));
}
}
#endregion
#region IList<T> methods
public int IndexOf(Thing item)
{
return _things.IndexOf(item);
}
public void Insert(int index, Thing item)
{
_things.Insert(index, item);
}
public void RemoveAt(int index)
{
_things.RemoveAt(index);
}
public Thing this[int index]
{
get
{
return _things[index];
}
set
{
_things[index] = value;
}
}
public void Add(Thing item)
{
_things.Add(item);
OnPropertyChanged("Things");
}
public void Clear()
{
_things.Clear();
}
public bool Contains(Thing item)
{
return _things.Contains(item);
}
public void CopyTo(Thing[] array, int arrayIndex)
{
_things.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _things.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(Thing item)
{
return _things.Remove(item);
}
public IEnumerator<Thing> GetEnumerator()
{
return _things.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _things.GetEnumerator();
}
#endregion
}
}
If you need to just download the application or look at it with a better interface you can find it here:
TestItemsControlInWinPhone8App
Thank you.
PS. I have read and, I think, followed all the advice I can find on Stackoverflow and elsewhere on the net about null handlers passed into OnPropertyChanged() methods and usage of ItemsControl that I can find.
_things needs to be ObservableCollection<Thing>. List<T> doesn't implement INotifyCollectionChanged, and hence doesn't raise notifications when its contents change. ObservableCollection<Thing> does, which will enable the UI to know when it needs to add items to the list.
The simple, easy, standard way of doing things is to expose an ObservableCollection as a property. If you replace the whole collection with a new one, raise PropertyChanged("Things"); when items are added/removed, the ObservableCollection will raise appropriate events without your needing to do anything. Experienced WPF folks reading your code will know what they're looking at.
To get it working the way you had in mind, you would have to call OnPropertyChanged("Things") in the methods that alter the Things collection; I haven't tested that, but I think it ought to work (the reason it might not is that the actual collection object returned by Things has not changed; the control might see that and choose not to update; but as I said I haven't tested that). Then you could bind Things to an ItemsSource on a control and maybe it ought to work. But then you could have other classes altering Things, because it's public. It would be a mess to try to chase down all the loose ends. Much easier to use ObservableCollection.
If you want to bind LotsOfThings itself to ItemsSource, you'll have to implement INotifyCollectionChanged on LotsOfThings, which gets to be a real hassle to rewrite all that by hand, and I'm not sure what it buys you. You could just make LotsOfThings a subclass of ObservableCollection<Thing> -- that starts you off with a complete and bulletproof INotifyCollectionChanged implementation, for free.
Ed Plunkett put me on the right path but the answer was a little more complicated so I'm laying out what I have learned in this answer.
First, I could have used ObservableCollection<T> - Ed is right. But I would have lost some of the IList<T> features I wanted. Besides I was trying to follow the practice in XAML Deep Dive ... (min 40-49) and they did not use ObservableCollection<T>.
Turns out I had mistakenly used INotifyPropertyChanged instead of INotifyCollectionChanged. The second interface has a slightly more complicated handler documented in the answer to this Stackoverflow question about calling OnCollectionChanged.
My research before asking this question found a bunch of ways to also get a null event handler. One was to call the handler with a misspelled property name (e.g. OnPropertyChanged("thing") when you should use OnPropertyChanged("Thing") because that is what the property is actually called - assuming you are dealing with properties and not collections. Another way to get a null event handler was to not bind the right object to the right content or container control. Here, take a look at "stack overflow C# why is my handler null".
And to finally put a stake in the heart of this problem I did a little research in the direction of Ed's hint that I become more familiar with the difference between List<T>, ObservableCollection<T> and INotifyPropertyChanged and found that excellent page.
I hope that helps. And thanks Ed; all my up-votes to you.
P.S. I have updated the repository of my test code to have the fix and git tag-ged the original version as broken and the fixed version as fixed.
What i m doing wrong?
Xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="26*"/>
<RowDefinition Height="63*"/>
<RowDefinition Height="67*"/>
</Grid.RowDefinitions>
<TextBlock Name ="title" Text="" TextWrapping="NoWrap" Grid.Row="0" Margin="25,10,25,0"/>
<Image Source="{Binding Path=BindImgURL}" HorizontalAlignment="Left" Height="164" Grid.Row="1" VerticalAlignment="Top" Width="306" Margin="152,0,0,0"/>
</Grid>
C# Code:
private string Id;
private string ImgURL;
public VideoDetails(string Id)
{
InitializeComponent();
this.Id = Id;
DetailsGenerator dg = new DetailsGenerator(Id);
this.ImgURL = "Dynamic source";
this.DataContext = BindImgURL;
MessageBox.Show(BindImgURL);
}
public string BindImgURL
{
get { return ImgURL; }
set
{ ImgURL = value; }
}
Displaying Source correct!
checked by MessageBox.But No image
what i did wrong? I have tried removing "path=" but doesn't worked
I imagine you either want to set DataContext to this
this.DataContext = this;
or change binding to current DataContext
<Image Source="{Binding}" .../>
at the moment Image will search for BindImgURL in current DataContext, which is set to BindImgURL and that is a string
Not related to your problem but I would also suggest you look into implementing INotifyPropertyChanged and raising PropertyChanged event for BindImgURL as, at the moment, after you set DataContext any change to BindImgURL won't be picked up by UI
I'm trying to write a windows phone 8 app using the mvvm pattern but I'm struggling with it.
I have a page with a list of persons which is binded to my PersonViewModel. That part is working fine. I then have 2 buttons in the application bar i.e. add or edit. When I want to edit a person, I select the person from the list, which then sets the CurrentPerson in my ViewModel. This in turns set a property in my MainViewModel which is used to store the currently selected person i.e.
App.MainViewModel.CurrentPerson = this.CurrentPerson;
When I want to add a new person, I use the same principal but I create a new person model.
App.MainViewModel.CurrentPerson = new PersonModel();
I then redirect to a page which contains the fields to handle a person, whether it is being added or edited and this is binded to a ViewModel called PersonEntryViewModel
Before I explain my problem, I want to let you know what I'm trying to achieve. I want the "Save" button in my application bar to get enabled once a certain amount of criteria have been met i.e. Name has been filled and has x characters, etc...
I can see what my problem is but I don't know how to resolve it.
Here is a simplied version of my PersonEntryViewModel:
public class PersonEntryViewModel : BaseViewModel
{
private PersonModel _currentPerson;
private bool _isNewPerson;
private ICommand _savePersonCommand;
private ICommand _cancelCommand;
private ICommand _titleTextChanged;
private bool _enableSaveButton;
public PersonEntryViewModel()
{
this.CurrentPerson = App.MainViewModel.CurrentPerson ?? new PersonModel();
}
public ICommand SavePersonCommand
{
get
{
return this._savePersonCommand ?? (this._savePersonCommand = new DelegateCommand(SavePersonAction));
}
}
public ICommand CancelCommand
{
get
{
return this._cancelCommand ?? (this._cancelCommand = new DelegateCommand(CancelAction));
}
}
public ICommand NameTextChanged
{
get
{
return this._nameTextChanged ?? (this._nameTextChanged = new DelegateCommand(NameTextChangedAction));
}
}
private void NameTextChangedAction(object actionParameters)
{
if (!string.IsNullOrEmpty(this._currentPerson.Name) && _currentPerson.Name.Length > 2)
{
EnableSaveButton = true;
}
}
private void CancelAction(object actionParameters)
{
Console.WriteLine("Cancel");
INavigationService navigationService = this.GetService<INavigationService>();
if (navigationService == null)
return;
navigationService.GoBack();
navigationService = null;
}
private void SavePersonAction(object actionParameters)
{
Console.WriteLine("Saving");
}
public PersonModel CurrentPerson
{
get { return this._currentPerson; }
set
{
if (this._currentPerson != value)
this.SetProperty(ref this._currentPerson, value);
}
}
public string PageTitle
{
get { return this._pageTitle; }
set { if (this._pageTitle != value) this.SetProperty(ref this._pageTitle, value); }
}
public bool IsNewPerson
{
get { return this._isNewPerson; }
set
{
if (this._isNewPerson != value)
{
this.SetProperty(ref this._isNewPerson, value);
if (this._isNewPerson)
this.PageTitle = AppResources.PersonEntryPageNewTitle;
else
this.PageTitle = AppResources.PersonEntryPageEditTitle;
}
}
}
public bool EnableSaveButton
{
get { return this._enableSaveButton; }
set { if (this._enableSaveButton != value) this.SetProperty(ref this._enableSaveButton, value); }
}
}
Here is part of my XAML:
<Grid x:Name="LayoutRoot" Background="Transparent" DataContext="{StaticResource PersonEntryViewModel}" >
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" DataContext="{Binding CurrentPerson, Mode=TwoWay}">
<Grid Margin="0,0,0,5">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border BorderBrush="{StaticResource PhoneAccentBrush}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderThickness="5"
Background="Transparent"
CornerRadius="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text="Name:"
Grid.Row="0"
Margin="12,0,0,0"/>
<TextBox Text="{Binding Name, Mode=TwoWay}"
Grid.Row="1">
<!--<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding NameTextChanged, Mode=OneWay}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>-->
</TextBox>
<TextBlock Text="Address:"
Grid.Row="2"
Margin="12,0,0,0"/>
<TextBox Text="{Binding Address, Mode=TwoWay}"
AcceptsReturn="True"
Height="200"
VerticalScrollBarVisibility="Visible"
TextWrapping="Wrap"
Grid.Row="3"/>
As you can see, my layoutRoot grid is binded to my ViewModel i.e. PersonEntryViewModel and the grid content panel containing my textboxes required for editing is binded to CurrentPerson.
Is that the correct way to do it? I need to bind the control to the CurrentPerson property which will contain data if the person is being edited and it will contain a new empty PersonModel if a new person is being added.
As it stands, that part is working. When I type some text in my field and click on the next one, it calls set the CurrentPerson relevant property which in turns calls the PersonModel. Click on the save button and I check the CurrentPerson, I can see it has all the various properties set.
As you can see in my PersonEntryViewModel, I've got other properties which are required. For example the EnableSaveButton, which technically should be set to true or false based on the validation of the various properites from the CurrentPerson object but I need this to be checked as the user is typing text in the various textbox and this is where I'm having a problem.
If I enable the following code:
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding NameTextChanged, Mode=OneWay}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
It doesn't get triggered in the PersonEntryViewModel where I really need it as this is where I want to set my EnableSaveButton property but I guess it makes sense as this code is binded to the Name textbox which is turn is binded to the CurrentPerson property which is my PersonModel.
If I move the code from the PersonEntryViewModel to the PersonViewModel
private ICommand _personTextChanged;
public ICommand PersonTextChanged
{
get
{
return this._personTextChanged ?? (this._personTextChanged = new DelegateCommand(PersonTextChangedAction));
}
}
private void PersonTextChangedAction(object actionParameters)
{
if (!string.IsNullOrEmpty(this._name) && this._name.Length > 2)
{
//EnableSaveButton = true;
Console.WriteLine("");
}
}
It gets triggered accordingly but then how do I get this information back to my PersonEntryViewModel which binded to the view where my 2 buttons (i.e. save & cancel) are located and the EnableSaveButton property is responsible for enabling the save button accordingly when set assuming that the Name is valid i.e. set and minlen is match for example.
Is the PersonEntryViewModel and using a CurrentPerson property with the current person being edited or added designed correctly or not and how am I to handle this scenario?
I hope the above makes sense but if I'm not clear about something, let me know and I'll try to clarify it.
Thanks.
PS: I posted another posted related to how to detect text change, but I figured it out but it's obviously not the problem. The problem seems more related to design.
Your design is unclear to me.
If you want to go with the current design itself, I would suggest you do the following thing.
Remove assigning the DataContext for grid in xaml.
In the code behind add:
var dataContext = new PersonEntryViewModel();
this.ContentPanel.DataContext = dataContext.CurrentPerson;
// After creating App bar
this.appBar.DataContext = dataContext;
//Your xaml code will look something like this:
<AppBar x:Name="appBar">
<Button x:Name="saveBtn" IsEnabled={Binding EnableSaveButton} />
<AppBar />
Ok, so I've been grinding away for hours now and still can't figure out why my data in my ViewModel is not being bound to my XAML in my main page. I even started an new project and implemented it fine the same way so I'm thinking it might have to do with namespaces or something I'm less familiar with.
When my application launches I create a global ViewModel in App.cs which I use to bind data to my XAML view.
public HomeViewModel ViewModel { get; private set; }
private void Application_Launching(object sender, LaunchingEventArgs e)
{
ViewModel = new HomeViewModel();
(App.Current as App).RootFrame.DataContext = (App.Current as App).ViewModel;
}
Then the HomeViewModel looks something like this:
public class HomeViewModel : INotifyPropertyChanged
{
/***View Model***/
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public HomeViewModel()
{
PropertyChanged = new PropertyChangedEventHandler(delegate { });
}
public Profile CurrentProfile; /*EDIT: Missing {get;set;} Which is necessary for
*any property, including ones below that I
*referenced in the XAML
*/
public string NotificationImage;
public ButtonPanelPath UniversalButtonPath;
public void setProfile(Profile p)
{
CurrentProfile = p;
NotifyPropertyChanged("CurrentProfile");
}
.
.
....rest of access methods and properties
Now when my program runs I am 100% sure that the data in HomeViewModel is getting updated and the NotifyPropertyChanged method is being called every time a new field is "set".
And this class is bound to the RootFrame right? So shouldn't I be able to access these fields in my main page's xaml? This is an example of part of the xaml in a stack panel in the main grid:
<Border BorderThickness="5" BorderBrush="Aqua" CornerRadius="20">
<StackPanel Name="profileInfo" DataContext="{Binding CurrentProfile}">
<TextBlock Text="{Binding FirstName}" Name="profileName" FontSize="26"
FontWeight="Bold" HorizontalAlignment="Center" />
<StackPanel Orientation="Horizontal">
<StackPanel>
<TextBlock Text="{Binding Level}" Name="userLevel" FontSize="32"
Margin="10,0,0,0"/>
<TextBlock Text="{Binding LevelName}" Name="levelName" FontSize="26"
Margin="10,0,0,0"/>
<TextBlock Text="{Binding PointsNeeded}" Name="pointsBar"
Margin="10,0,0,0"/>
</StackPanel>
<Image x:Name="levelIcon" Source="{Binding PictureUrl}"
Margin="15,0,0,0"/>
</StackPanel>
</StackPanel>
</Border>
So here Level, LevelName, PointsNeeded and PictureUrl are all public fields in Profile (or CurrentProfile which is the specific instance of Profile I'm referencing). I tried Profile.[field] but that didn't work either. If anyone could tell me what I'm missing to complete the binding it would be greatly appreciated.
By the way the namespaces are as follows if that means anything
-MainPage is in MyApp.src.pages
-App is in MyApp
-HomeViewModel is in MyApp.src.classes
Thanks in advance for your helpful solutions/comments, if you'd like more data/info please just ask.
The binding you are looking for is {Binding Proptery.SubProperty}.
So in your case for example {Binding CurrentProfile.Level}.
You are having an instance of your "HomeViewModel" in the DataContext, so you can access all of its propteries. If there is a complex type as a property, you have to access the property, the instance of the complex type not the type, to access its "sub"-properties.
Hope it helps.
I have parent window which has textBox called "SchoolName", and a button called "Lookup school Name".
That Button opens a child window with list of school names. Now when user selects school Name from child window, and clicks on "Use selected school" button. I need to populate selected school in parent view's textbox.
Note: I have adopted Sam’s and other people’s suggestion to make this code work. I have updated my code so other people can simply use it.
SelectSchoolView.xaml (Parent Window)
<Window x:Class="MyProject.UI.SelectSchoolView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Parent" Height="202" Width="547">
<Grid>
<TextBox Height="23" Width="192"
Name="txtSchoolNames"
Text="{Binding Path=SchoolNames, UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}"
/>
<Label Content="School Codes" Height="28" HorizontalAlignment="Left"
Margin="30,38,0,0" Name="label1" VerticalAlignment="Top" />
<Button Content="Lookup School Code" Height="30" HorizontalAlignment="Left"
Margin="321,36,0,0" Name="button1" VerticalAlignment="Top" Width="163"
Command="{Binding Path=DisplayLookupDialogCommand}"/>
</Grid>
</Window>
SchoolNameLookup.xaml (Child Window for Look up School Name)
<Window x:Class="MyProject.UI.SchoolNameLookup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="SchoolCodeLookup" Height="335" Width="426">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="226*" />
<RowDefinition Height="70*" />
</Grid.RowDefinitions>
<toolkit:DataGrid Grid.Row="0" Grid.Column="1" x:Name="dgSchoolList"
ItemsSource="{Binding Path=SchoolList}"
SelectedItem="{Binding Path=SelectedSchoolItem, Mode=TwoWay}"
Width="294"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
CanUserSortColumns="True"
SelectionMode="Single">
<Button Grid.Row="1" Grid.Column="1" Content="Use Selected School Name"
Height="23" Name="btnSelect" Width="131" Command="{Binding
Path=UseSelectedSchoolNameCommand}" />
</Grid>
</Window>
SchoolNameLookupViewModel
private string _schoolNames;
public string SchoolNames
{
get { return _schoolNames; }
set
{
_schoolNames= value;
OnPropertyChanged(SchoolNames);
}
}
private ICommand _useSelectedSchoolNameCommand;
public ICommand UseSelectedSchoolNameCommand{
get
{
if (_useSelectedSchoolNameCommand== null)
_useSelectedSchoolNameCommand= new RelayCommand(a =>
DoUseSelectedSchollNameItem(), p => true);
return _useSelectedSchoolNameCommand;
}
set
{
_useSelectedSchoolNameCommand= value;
}
}
private void DoUseSelectedSchoolNameItem() {
StringBuilder sfiString = new StringBuilder();
ObservableCollection<SchoolModel> oCol =
new ObservableCollection<SchoolModel>();
foreach (SchoolModel itm in SchollNameList)
{
if (itm.isSelected) {
sfiString.Append(itm.SchoolName + "; ");
_schoolNames = sfiString.ToString();
}
}
OnPropertyChanged(SchoolNames);
}
private ICommand _displayLookupDialogCommand;
public ICommand DisplayLookupDialogCommand
{
get
{
if (_displayLookupDialogCommand== null)
_displayLookupDialogCommand= new
RelayCommand(a => DoDisplayLookupDialog(), p => true);
return _displayLookupDialogCommand;
}
set
{
_displayLookupDialogCommand= value;
}
}
private void DoDisplayLookupDialog()
{
SchoolNameLookup snl = new SchoolNameLookup();
snl.DataContext = this; //==> This what I was missing. Now my code works as I was expecting
snl.Show();
}
My solution is to bind both the windows to the same ViewModel, then define a property to hold the resulting value for codes, lets call it CurrentSchoolCodes, Bind the label to this property. Make sure that CurrentSchoolCodes raises the INotifyPropertyChanged event.
then in the DoUseSelectedSchoolNameItem set the value for CurrentSchoolCodes.
For properties in your models I suggest you to load them as they are required(Lazy Load patttern). I this method your property's get accessor checks if the related field is still null, loads and assigns the value to it.
The code would be like this code snippet:
private ObservableCollection<SchoolModel> _schoolList;
public ObservableCollection<SchoolModel> SchoolList{
get {
if ( _schoolList == null )
_schoolList = LoadSchoolList();
return _schoolList;
}
}
In this way the first time your WPF control which is binded to this SchoolList property tries to get the value for this property the value will be loaded and cached and then returned.
Note: I have to say that this kind of properties should be used carefully, since loading data could be a time consuming process. And it is better to load data in a background thread to keep UI responsive.
The Solution Sam suggested here is a correct one.
What you didn't get is that you should have only one instance of you viewmodel and your main and child page should refer to the same one.
Your viewmodel should be instanciated once: maybe you need a Locator and get the instance there... Doing like this the code in your ctor will fire once, have a look at the mvvmLight toolkit, I think it will be great for your usage, you can get rid of those Classes implementing ICommand too...
You can find a great example of using that pattern here:
http://blogs.msdn.com/b/kylemc/archive/2011/04/29/mvvm-pattern-for-ria-services.aspx
basically what happens is this:
you have a Locator
public class ViewModelLocator
{
private readonly ServiceProviderBase _sp;
public ViewModelLocator()
{
_sp = ServiceProviderBase.Instance;
// 1 VM for all places that use it. Just an option
Book = new BookViewModel(_sp.PageConductor, _sp.BookDataService);
}
public BookViewModel Book { get; set; }
//get { return new BookViewModel(_sp.PageConductor, _sp.BookDataService); }
// 1 new instance per View
public CheckoutViewModel Checkout
{
get { return new CheckoutViewModel(_sp.PageConductor, _sp.BookDataService); }
}
}
that Locator is a StaticResource, in App.xaml
<Application.Resources>
<ResourceDictionary>
<app:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</ResourceDictionary>
</Application.Resources>
in your views you refer you viewmodels trough the Locator:
DataContext="{Binding Book, Source={StaticResource Locator}}"
here Book is an instance of BookViewModel, you can see it in the Locator class
BookViewModel has a SelectedBook:
private Book _selectedBook;
public Book SelectedBook
{
get { return _selectedBook; }
set
{
_selectedBook = value;
RaisePropertyChanged("SelectedBook");
}
}
and your child window should have the same DataContext as your MainView and work like this:
<Grid Name="grid1" DataContext="{Binding SelectedBook}">