Binding a C# class to XAML for WP7 - c#

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.

Related

WPF - Checkbox Command not firing

I am writing a WPF app using the MVVM pattern and I am having the following problem: I have bound a command to a checkbox in my UI however my event handler is not being called when the check box is clicked. I have used the same approach to bind other UI elements such as buttons and it seems to work alright for them. The relevant xaml is as follows:
<ListBox ItemsSource="{Binding ElementsMethods}" Height="auto" x:Name="MethodsListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FormattedEM}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Started"/>
<Checkbox IsChecked="{Binding Started} Command="{Binding elementMethodCheckboxChangeCommand}"> </CheckBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Finished"/>
<CheckBox IsChecked="{Binding Finished}"></CheckBox>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>IsChecked="{Binding Finished}
Where elementMethodCheckboxChangeCommand is a public property of type ICommand in my viewmodel class:
public ICommand elementMethodCheckboxChangeCommand { get; set; }
the concrete class used to set this property is named relay command:
elementMethodCheckboxChangeCommand = new RelayCommand(new Action<object>(elementMethodCheckboxChange));
where elementMethodCheckboxChange is a public void function taking a parameter of type object.
The implementation of the relaycommand class is as follows:
class RelayCommand : ICommand
{
private Action<object> _action;
public RelayCommand(Action<object> action)
{
_action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (parameter != null)
{
_action(parameter);
}
else
{
_action("Hello world");
}
}
public event EventHandler CanExecuteChanged;
}
Like I said above I have used this same approach to bind to buttons in my UI and they have worked as expected, however when I click the checkbox nothing happens at all, and my event handler is not executed.
I hope someone can help me out here as this problem is starting to become really frustrating - please ask if you need any additional information. Thank you all in advance :)
You should specify a RelativeSource of the binding when you want to bind to a property of the view model inside an `ItemTemplate:
<CheckBox ... Command="{Binding DataContext.elementMethodCheckboxChangeCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}"/>
The default DataContext is the current item in the ItemsSource and this one has no elementMethodCheckboxChangeCommand property to bind to.
Making the property static is not a very good solution.

Two-way binding and filtering of ObservableCollection in WPF/MVVM

I am learning MVVM pattern while refactoring an app to MVVM.
I have a model class Machine that provides a list of installations in a form of ObservableCollection<Installation> Installations.
In one of the windows (views) I need to display only those installations that have updates (thus meet the following criteria):
private void InstallationsToUpdateFilter(object sender, FilterEventArgs e)
{
var x = (Installation)e.Item;
bool hasNewVersion = ShowAllEnabledInstallations ? true : x.NewVersion != null;
bool isSetAndOn = !String.IsNullOrEmpty(x.Path) && x.CheckForUpdatesFlag;
e.Accepted = isSetAndOn && hasNewVersion;
}
private void OnFilterChanged()
{
installationsToUpdateSource?.View?.Refresh();
}
I am doing this by filtering in my ViewModel:
class NewVersionViewModel : ViewModelBase
{
private Machine machine = App.Machine;
...
public NewVersionViewModel(...)
{
...
InstallationsToUpdate.CollectionChanged += (s, e) =>
{
OnPropertyChanged("NewVersionsAvailableMessage");
OnFilterChanged();
};
installationsToUpdateSource = new CollectionViewSource();
installationsToUpdateSource.Source = InstallationsToUpdate;
installationsToUpdateSource.Filter += InstallationsToUpdateFilter;
}
public ObservableCollection<Installation> InstallationsToUpdate
{
get { return machine.Installations; }
set { machine.Installations = value; }
}
internal CollectionViewSource installationsToUpdateSource { get; set; }
public ICollectionView InstallationsToUpdateSourceCollection
{
get { return installationsToUpdateSource.View; }
}
...
}
This is done by custom ListView:
<ListView ItemsSource="{Binding InstallationsToUpdateSourceCollection}" ... >
...
<ListView.ItemTemplate>
<DataTemplate>
<Grid ...>
<Grid ...>
<CheckBox Style="{StaticResource LargeCheckBox}"
IsChecked="{Binding Path=MarkedForUpdate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding Path=HasNewVersion}"
/>
</Grid>
<Label Content="{Binding Path=InstalledVersion.Major}" Grid.Column="1" Grid.Row="0" FontSize="50" FontFamily="Segoe UI Black" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,-10,0,0"/>
...
<Grid.ContextMenu>
<ContextMenu>
...
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
All of this works - until I try to "send" <CheckBox IsChecked="{Binding Path=MarkedForUpdate... back to my model - so it will be stored there.
How it can be done? (Can I have some kind of setter on ICollectionView?)
Current architecture can be changed. What I ultimately need:
Display items (installations) from model in ListView (currently: works)
Filter/Show only installations that meet some criteria (currentrly: works)
Reflect changes in MarkedForUpdate checkbox back to model (currently: not working)
I've googled a lot but was unable to find a relevant solution or suggestions.
Any help would be greatly appreciated. Thanks!
I figured the problem out. Although it was a silly mistake, I still want to share it to save someone's time.
The model itself updates in the configuration described above. The problem was that what model property (Machine.Installations in my case) did not implement INotifyPropertyChanged interface so other Views (through their corresponding ViewModels) were not aware of changes. Thus one should use OnPropertyChanged/RaisePropertyChanged not only in ViewModel, but in Model as well.
Hope this may help someone.

WPF switch multiple control templates with DataType property fails

I am trying to make a contactlist with 2 different types of contacts, FysiekContactPersoon (Fysical persons) and WinkelOfBedrijf (Corporates). They both are inherited from the class ContactPersoon.
my MainWindow.xaml.cs
public partial class MainWindow : Window
{
ContactPersoonViewModel _viewmod = null;
public ContactPersoonViewModel ViewMod
{
get { _viewmod ??= new ContactPersoonViewModel(); return _viewmod; }
set => _viewmod = value;
}
public MainWindow()
{
InitializeComponent();
ViewMod.Import();
DataContext = ViewMod;
}
private void InfoButton_Click(object sender, RoutedEventArgs e)
{
DialogInfo dlg = new DialogInfo(ViewMod) { Owner = this };
if (dlg.ShowDialog() == true) { }
}
}
When the user selects a contact from a datagrid on mainwindow and presses the Info button, the dialog window opens.I have created two templates that normally have to be applied each to its corresponding class.But the dialogwindow is empty, despite the fact that the current item is shown properly in the viewModel when debugging.
My dialoginfo.xaml (simplified):
<ContentControl DataContext="{Binding CurrentCP}" Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="x:Type local:FysiekeContactpersoon">
<StackPanel Margin="5,5,5,5" HorizontalAlignment="Center" VerticalAlignment="Center">
<Label Content="Person:" HorizontalAlignment="Center" VerticalAlignment="Center" Width="114" Height="26" />
<TextBox x:Name="ContactNaam" HorizontalAlignment="Center" TextWrapping="Wrap" Text="{Binding Naam}" VerticalAlignment="Center" Width="218" Height="22"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="x:Type local:WinkelOfBedrijf">
<StackPanel Margin="5,5,5,5" HorizontalAlignment="Center" VerticalAlignment="Center">
<Label Content="Corporate:" HorizontalAlignment="Center" VerticalAlignment="Center" Width="114" Height="26" />
<TextBox x:Name="ContactNaam" HorizontalAlignment="Center" TextWrapping="Wrap" Text="{Binding Naam}" VerticalAlignment="Center" Width="218" Height="22"/>
</StackPanel>
</DataTemplate>
</ContentControl.Resources>
and my dialoginfo.xaml.cs
public partial class DialogInfo : Window
{
ContactPersoonViewModel _viewModel = null;
public ContactPersoonViewModel ViewModel { get => _viewModel; set => _viewModel = value; }
public DialogInfo(ContactPersoonViewModel vm)
{
ViewModel = vm;
InitializeComponent();
DataContext = vm.CurrentCP;
}
What am I doing wrong here? I was through a lot of similar threads, mostly pointing at this solution as correct and the simplest one, comparing with DataTemplateSelector or Property setters and triggers (which also aren't working with me- I've tried :().
Moreover, I have each second time a compilation fail "The key had already been added" of something, but the next compilation is perfectly succeeded after no code has been changed at all(WTF??!).Needless to say, how disappointed I am in XAML. I would appreciate some help in the form of a piece of a suitable code, or a very good tutorial link.
It looks like your code should basically work. The only problem I found is you type declaration on the DataTemplate.
For properties of type Type like Style.TargetType the XAML engine will convert the string representation of a type to an actual Type instance.
But this is not the case for properties like DataTemplate.DataType. Since DataTemplate defines the property DataType of type object, there will be no internal conversion from string to Type.
This is because DataTemplate.DataType expects a string for XML types and Type for objects.
Because you assigned a string to DataTemplate.DataType, no object type is resolved, as the data object is expected to be a XML object.
Using x:Type in order to define a Type rather than a string is correct, but you simply forgot to mark the declaration as markup extension using curly braces! Without this braces you are just defining a string value.
The correct syntax is:
<DataTemplate DataType="{x:Type local:FysiekeContactpersoon}">
...
</DataTemplate>

How do you take input of a TextBox and return it in a TextBlock

I am new to MVVM and I am trying to type a string into a textbox and it return on a textblock on another page.
In my Views folder I have this code in xaml which is the textbox that I want to type into:
<TextBox x:Name="date" Text="{Binding Date}" Grid.Row="0" TextAlignment="Right" TextWrapping="Wrap" Margin="0 10 0 1" Padding="1" />
This is a different wpf page that has the textblock and I want what was typed in the textbox to appear here:
<TextBlock Grid.Row="0" TextAlignment="Right" TextWrapping="Wrap" Margin="0 0 0 2" Padding="1" Text="{Binding Date}" />
In my Model folder I have the class Data Entry which looks like this:
public class DataEntry
{
public string Date { get; set; }
}
In my ViewModels folder I have:
namespace FumeHood1._0._0.ViewModels
{
public class MainViewModel : INotifyPropertyChanged
{
public DataEntry DataEntry { get; set; }
private string date;
public string Date
{
get { return date; }
set
{
date = value;
OnPropertyChanged(nameof(Date));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have been looking everywhere and I cant find the right way to do it. If anyone could help it would be amazing. Just trying to make this MVVM pattern work and make more sense to me.
First, set the DataContext of your view.
public partial class MainWindow : Window
{
public MainWindow() {
InitializeComponent();
DataContext = new MainViewModel();
}
}
Or in xaml : (NOT both at the same time)
<Window>
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
</Window>
And make sure the same instance of MainViewModel is used in both pages.
Second, carefully configure your bindings to act as intended:
<TextBox Text="{Binding Date, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}"/>
<TextBlock Text="{Binding Date}" />
Note that UpdateSourceTrigger=PropertyChanged makes sure that the view model property is updated while the user types. Mode=OneWayToSource only updates the view model property from the TextBox Text property, but not the other way round.

OnPropertyChanged method is not firing

In WP8 app, i have few controls where i bind the foreground color which i am changing in the codebehind. But OnPropertyChanged is not firing when the user event happened.
I have defined this binding "ControlForeground" in my textblock and radiobutton data template controls in it. I am trying to change the Foreground color whenever user presses the button. But my new color assignment is not updating the UI. Anything i am missing here?
In XAML,
<TextBlock x:Name="lblTileColor" TextWrapping="Wrap" Text="Selected color:" Foreground="{Binding ControlForeground, Mode=TwoWay}"/>
<TextBlock x:Name="lblTileColor2" TextWrapping="Wrap" Text="App bg:" Foreground="{Binding ControlForeground, Mode=TwoWay}"/>
<RadioButton x:Name="accentColor" IsChecked="true" BorderBrush="White" Foreground="{Binding ControlForeground, Mode=TwoWay}">
<RadioButton.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Width="25" Height="25" Fill="{StaticResource PhoneAccentBrush}"/>
<TextBlock Width="10"/>
<TextBlock x:Name="lblDefaultAccent" Text="Default accent color" Foreground="{Binding ControlForeground, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>
</RadioButton.ContentTemplate>
</RadioButton>
<Button x:name="UpdateColor" click="update_btn"/>
In C#,
public class ColorClass : INotifyPropertyChanged
{
private SolidColorBrush _ControlForeground;
public SolidColorBrush ControlForeground
{
get
{
return _ControlForeground;
}
set
{
_ControlForeground = value;
OnPropertyChanged("ControlForeground");
}
}
public ColorClass() { }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public class ColorPage:PhoneApplicationPage{
public ObservableCollection<ColorClass> TestCollection { get; private set; }
public void update_btn(object sender, EventArgs e){
TestCollection.Add(new ColorClass()
{
ControlForeground = new SolidColorBrush(Colors.Red)
});
}
}
For your 2nd problem (not being able to bind controls inside your data template), this is because these controls will use the data context of the their parent template not the data context of the page.
To fix this, you'll have to tell these controls the element name with the data context and give it full path of your property.
<TextBlock
x:Name="lblDefaultAccent"
Text="Default accent color"
Foreground="{Binding DataContext.ControlForeground,
ElementName=LayoutRoot, Mode=TwoWay}"/>
As you can see above you have to specify the element name. In case you bound this using this.DataContext = colorClass then the element name will be the name of the outer grid in your xaml, defaulted as LayoutRoot
You can only bind an ObservableCollection to controls which expect it, like a ListBox or LongListSelector. Additionally, adding a Brush to the TestCollection doesn't fire the non-functional notification since it doesn't call the setter of that property, just modifies the existing object.
Make TestCollection a type ColorClass and change the .Add stuff to just change the ColorClass.ControlForeground property and this should "just work."

Categories