Ok, I tried it several ways, but none worked as it should be in my case. I have a simple Window with a single ComboBox. I am changing the code to MVVM, so now everything is still in the Code-Behind and should go to a ViewModel, etc.
But even on the first step (binding the ViewModel to the View/Window) I don't seem to be able to bind them together.
My Window XAML:
<Window x:Class="CustomerGuidance.ClientWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:VM="clr-namespace:CustomerGuidance.ViewModels"
Title="Stop'n'Go - Client" Height="22" Width="229"
Loaded="ClientWindow_OnLoaded" WindowStyle="None"
WindowStartupLocation="Manual" Top="0" Left="0"
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True">
<Window.DataContext>
<VM:EmployeeViewModel />
</Window.DataContext>
<Canvas Background="Gainsboro">
<ComboBox Name="EmployeesComboBox"
ItemsSource="{Binding EmployeeEntries}"
Width="192" FontFamily="Arial" FontSize="14">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Lastname}" />
<TextBlock Text=", " />
<TextBlock Text="{Binding Surname}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Canvas>
The ViewModel looks like this:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace CustomerGuidance.ViewModels
{
public class EmployeeViewModel : INotifyPropertyChanged
{
public EmployeeViewModel()
{
}
public static ObservableCollection<ServerWindow.EmployeeEntry> EmployeeEntries { get; set; } = new ObservableCollection<ServerWindow.EmployeeEntry>();
private string _surname;
private string _lastname;
private int _id;
public string Surname
{
get { return _surname; }
set
{
if (_surname == value)
return;
_surname = value;
NotifyPropertyChanged("Surname");
}
}
public string Lastname
{
get { return _lastname; }
set
{
if (_lastname == value)
return;
_lastname = value;
NotifyPropertyChanged("Lastname");
}
}
public int Id
{
get { return _id; }
set
{
if (_id == value)
return;
_id = value;
NotifyPropertyChanged("Id");
}
}
public virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I get the following error message: "The Name "EmployeeViewModel" is not available in the namespace "clr-namespace:CustomerGuidance.ViewModels".And now the question: What am I missing? How can I bind the ViewModel to my window-XAML?
You should build your code for the errors to disappear.
It's because the namespace is not yet available in the assembly the designer relies on (your program) before it has been built.
Related
I'm wrestling with these last few days. I found lots of links, but none of them really helped me. I'm quite a beginner in WPF.
All I need is to reach SelectedItem property in nested ListView.
Outter ListView binding works, of course.
What I tried after some research and doesn't work, even I don't really understand why it dosnt work:
<Window x:Class="ListViewRef.View.ListViewWindow"
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:vm="clr-namespace:ListViewRef.ViewModel"
xmlns:local="clr-namespace:ListViewRef.View"
mc:Ignorable="d"
Title="Nested List Views" Height="450" Width="800">
<Window.DataContext>
<vm:MainVM/>
</Window.DataContext>
<StackPanel x:Name="Global">
<TextBlock Text="{Binding MainTitle}"/>
<ListView ItemsSource="{Binding Path=SourceCollection}"
SelectedItem="{Binding Path=OutterSelectedItem}"
>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="Now second ListView:"/>
<ListView ItemsSource="{Binding Strings}"
SelectedItem="{Binding Path=NestedSelectedItem,
RelativeSource={RelativeSource AncestorType=vm:MainVM},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
></ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>
And ViewModel:
using ListViewRef.Model;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace ListViewRef.ViewModel
{
public class MainVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string mainTitle;
public string MainTitle {
get { return mainTitle; }
set { mainTitle = value; OnPropertyChanged(nameof(MainTitle)); }
}
private string nestedSelectedItem;
public string NestedSelectedItem {
get { return nestedSelectedItem; }
set
{
nestedSelectedItem = value;
MessageBox.Show("NestedSelectedItem: " + nestedSelectedItem);
OnPropertyChanged(nameof(NestedSelectedItem));
}
}
private string outterSelectedItem;
public string OutterSelectedItem {
get { return outterSelectedItem; }
set
{
outterSelectedItem = value;
MessageBox.Show("OutterSelectedItem: " + OutterSelectedItem);
OnPropertyChanged(nameof(OutterSelectedItem));
}
}
public ObservableCollection<ClassWithObsList> SourceCollection { get; set; }
public MainVM()
{
MainTitle = "Title of the Grid";
SourceCollection = new ObservableCollection<ClassWithObsList> {
new ClassWithObsList("First Title", new ObservableCollection<string> { "First", "Second"}),
new ClassWithObsList("Second Title", new ObservableCollection<string> { "Third", "Fourth"}),
new ClassWithObsList("Third Title", new ObservableCollection<string> { "Fifth", "Sixth"}),
};
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Model class:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace ListViewRef.Model
{
public class ClassWithObsList : INotifyPropertyChanged
{
private string title;
public string Title {
get { return title; }
set {
title = value;
OnPropertyChanged(nameof(Title));
}
}
public ObservableCollection<string> Strings { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public ClassWithObsList(string title, ObservableCollection<string> strings)
{
Title = title ?? throw new ArgumentNullException(nameof(title));
Strings = strings ?? throw new ArgumentNullException(nameof(strings));
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In
SelectedItem="{Binding Path=NestedSelectedItem,
RelativeSource={RelativeSource AncestorType=vm:MainVM}, ...}
the type vm:MainVM is not an ancestor type of the inner ListView, because it not part of a visual or logical tree.
The AncestorType must be a UI element, e.g. the outer ListView. You would access the property by a nested property Path via its DataContext:
SelectedItem="{Binding Path=DataContext.NestedSelectedItem,
RelativeSource={RelativeSource AncestorType=ListView}, ...}
As a note, since you are not setting the View property of the ListViews, you could as well use the simpler base class ListBox instead of ListView:
<ListBox ItemsSource="{Binding Path=SourceCollection}"
SelectedItem="{Binding Path=OutterSelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="Now second ListView:"/>
<ListBox ItemsSource="{Binding Strings}"
SelectedItem="{Binding Path=DataContext.NestedSelectedItem,
RelativeSource={RelativeSource AncestorType=ListBox},
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Problem
I am trying to bind a ComboBox's SelectedItem to a custom class but this does not update when the property is changed.INotifyPropertyChanged is implemented.
The DataContext
The DataContext is a custom class which contains many properties, but an extract of this is below. You can see it implements INotifyPropertyChanged and this called when the two properties are changed.
public class BctsChange : INotifyPropertyChanged
{
#region declarations
private byContact _Engineer;
public byContact Engineer
{
get { return _Engineer; }
set
{
_Engineer = value;
NotifyPropertyChanged("Engineer");
OnEngineerChanged();
}
}
private BctsSvc.DOSets _LeadingSet;
public BctsSvc.DOSets LeadingSet
{
get { return _LeadingSet; }
set { _LeadingSet = value; NotifyPropertyChanged("LeadingSet"); }
}
#endregion
#region INotify
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public BctsChange()
{
Engineer = new byContact(Environment.UserName);
}
private void OnEngineerChanged()
{
if (Engineer != null)
{
BctsSvc.DOSets leadSet = GetLeadingSetFromDeptCode(Engineer.DeptCode);
if (leadSet == null) return;
LeadingSet = leadSet;
}
}
private static BctsSvc.DOSets GetLeadingSetFromDeptCode(string DeptCode)
{
BctsSvc.BctsServiceSoapClient svc = new BctsSvc.BctsServiceSoapClient();
BctsSvc.DOSets setX = svc.GetSetFromDeptCode(DeptCode);
return setX;
}
}
The Window XAML
I have several controls on the window, but to keep the code simple I believe the following extract will suffice.
<Window x:Class="MyNamespace.wdSubmit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:MyNamespace"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Name="ucReqForm"
Title="wdSubmit" >
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<GroupBox Header="Engineer Details" Name="grpOwnerDetails" >
<StackPanel Orientation="Vertical">
<Grid VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="35"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Engineer.FullName, FallbackValue='Please select an engineer by clicking →', Mode=OneWay}" Margin="5,0" IsEnabled="True" FontStyle="Italic" />
<Button Content="{StaticResource icoSearch}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Column="1" Height="23" Name="btnSelectEngineer" Margin="0,0,5,0" HorizontalAlignment="Stretch" ToolTip="Search for an engineer responsible" Click="btnSelectEngineer_Click" />
</Grid>
<ComboBox Height="23" x:Name="ddSet2" Margin="5,0" ItemsSource="{Binding LeadingSets, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" SelectedItem="{Binding LeadingSet, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,NotifyOnTargetUpdated=True}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SetName}" ToolTip="{Binding HelpInfo}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<my:LabelledDropdown Height="23" x:Name="ddSet" Margin="5,0" ItemsSource="{Binding LeadingSets, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" SelectedItem="{Binding LeadingSet, Mode=TwoWay,NotifyOnTargetUpdated=True,NotifyOnSourceUpdated=True}" Label="e.g. BodyHardware">
<my:LabelledDropdown.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SetName}" ToolTip="{Binding HelpInfo}"/>
</DataTemplate>
</my:LabelledDropdown.ItemTemplate>
</my:LabelledDropdown>
</StackPanel>
</GroupBox>
</StackPanel>
</Window>
The above extract contains:
A Label that contains a contact's name, and a button to search for a contact, bound to the FullName of the Engineer
A ComboBox that contains departments within the company, bound to an ObservableCollection<DOSets>, which contains a list of departments
Two ComboBoxes, one which is a custom one and the other which is temporary to ensure the bug is not within the control. These are Databound to LeadingSet
Window Code Behind
In the code behind I set the DataContext to CurrentChange. When the user wants to select a different Engineer then this will update the selected department for the engineer in CurrentChange.
When the user changes the engineer, the data binding for the engineer is updated, but the selected department (Leading Set) isn't.
//Usings here
namespace MyNamespace
{
public partial class wdSubmit : Window, INotifyPropertyChanged
{
private BctsSvc.BctsServiceSoapClient svc;
private BctsChange _CurrentChange;
public BctsChange CurrentChange
{
get { return _CurrentChange; }
set { _CurrentChange = value; OnPropertyChanged("CurrentChange"); }
}
private List<BctsSvc.DOSets> _LeadingSets;
public List<BctsSvc.DOSets> LeadingSets
{
get
{
return _LeadingSets;
}
}
public wdSubmit()
{
InitializeComponent();
svc = new BctsSvc.BctsServiceSoapClient();
_LeadingSets = svc.GetLeadSets().ToList();
OnPropertyChanged("LeadingSets");
this._CurrentChange = new BctsChange();
this.DataContext = CurrentChange;
CurrentChange.PropertyChanged += new PropertyChangedEventHandler(CurrentChange_PropertyChanged);
}
void CurrentChange_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("CurrentChange");
OnPropertyChanged(e.PropertyName);
}
private void btnSelectEngineer_Click(object sender, RoutedEventArgs e)
{
byContact newContact = new frmSearchEngineer().ShowSearch();
if (newContact != null)
{
CurrentChange.Engineer = newContact;
PropertyChanged(CurrentChange, new PropertyChangedEventArgs("LeadingSet"));
PropertyChanged(CurrentChange.LeadingSet, new PropertyChangedEventArgs("LeadingSet"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(CurrentChange, new PropertyChangedEventArgs(propertyName));
}
}
}
I've realised the problem may be due to the LeadingSet, returned when the engineer is changed, being a different instance to that in the ObservableCollection.
I searched in this forum but I was unable to find a solution for my specific scenario.
I`m trying to understand WPF and MVVM and I build a simple WPF for this.
My Data Model is (I Implemented INotifyPropertyChanged here and the constructor initializes all properties):
namespace MyApp.ui.Models
{
public class Server : INotifyPropertyChanged
{
private int id;
public int ID
{
get { return id; }
set { id = value; }
}
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged(Name); }
}
private string ipAddress;
public string IPAddress
{
get { return ipAddress; }
set { ipAddress = value; OnPropertyChanged(IPAddress); }
}
public Server(int ServerID, string ServerName, string ServerIpAddress)
{
ID = ServerID;
Name = ServerName;
IPAddress = ServerIpAddress;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null)
{
handler(this, new PropertyChangedEventArgs( propertyName ) );
}
}
}
}
My ViewModel (used by WPF Code Behind):
namespace MyApp.ui.ViewModels
{
public class ServersViewModel
{
private ObservableCollection<Server> server;
public ObservableCollection<Server> Servers
{
get { return server; }
set { server = value; }
}
public ServersViewModel()
{
Servers = new ObservableCollection<Server>
{
new Server(001, "Server001", #"192.168.254.3"),
new Server(002, "Server002", #"100.92.0.200"),
new Server(003, "Server003", #"64.32.0.3"),
new Server(004, "Server004", #"172.10.0.4"),
new Server(005, "Server005", #"165.23.0.233"),
new Server(006, "Server006", #"81.22.22.6"),
new Server(007, "Server007", #"10.10.0.7")
};
}
public void ChangeServerNames()
{
//Before Change
foreach (var item in Servers)
{
MessageBox.Show(item.Name);
}
int count = 1000;
foreach (var item in Servers)
{
item.Name = "Server" + count.ToString();
count += 1000;
}
//After Change
foreach (var item in Servers)
{
MessageBox.Show(item.Name);
}
}
}
}
My WPF Main View (Main Menu) loads a Custom user control (ExplorerView) with the following XAML code (Contains a listbox and each listbox item contains 1 checkbox + image + textblock)
<UserControl x:Class="MyApp.ui.Views.ExplorerView"
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:MyApp.ui.Views"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="200">
<Grid>
<ListBox ItemsSource="{Binding Servers}" Margin="2">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox VerticalContentAlignment="Center" Margin="4">
<StackPanel Orientation="Horizontal">
<Image Source="/resources/server64.png" Height="30" Margin="4"></Image>
<TextBlock Text="{Binding Name}"
VerticalAlignment="Center" Margin="4"></TextBlock>
</StackPanel>
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
Finally the MainView Code Behind loads the ServersViewModel so the ExplorerView Control can Bind the data.
namespace MyApp.ui
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ServersViewModel context { get; set; }
public MainWindow()
{
InitializeComponent();
context = new ServersViewModel();
DataContext = context;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
context.ChangeServerNames();
}
}
}
That said, I have 2 Questions:
1) As you can see, in the MainView I implemented a Button click event that calls into ServersViewModel.ChangeServerNames() Method. The problem is that my TextBlock in ExplorerView Control does not show the updated data.
I ChangeServerNames() I also use a MessageBox to show the Values Before and After the change, and I see that the values are changing, not sure why the ListBox/TextBlock is not updating...!!! (I already tested many other possible solutions, but I can`t get it working...)
2) I read that the CodeBehind in MainView (and all other views) should only contain the InitializeComponent(); and "DataContext = context;" at Maximum...
If that is true, where the Events for button clicks and others should be placed?
Finally the code for the MainWindow XAML:
<Window
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:MyApp.ui"
xmlns:Views="clr-namespace:MyApp.ui.Views"
x:Class="MyApp.ui.MainWindow"
mc:Ignorable="d"
Title="Server" MinHeight="720" MinWidth="1024"
Height ="720" Width="1024">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="41*"/>
<RowDefinition Height="608*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<GridSplitter Grid.Column="1" Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Background="Gray"
ShowsPreview="True"
Width="4" Margin="0,2,0,4"
/>
<Views:MenuView Grid.ColumnSpan="3"/>
<Views:FooterView Grid.Row="2" Grid.ColumnSpan="3" />
<Views:ExplorerView Grid.Column="0" Grid.Row="1" />
<!--Temp Tests-->
<StackPanel Margin="12" Grid.Column="3" Grid.Row="1" Width="Auto" Height="Auto" Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left">
<Button Margin="4" Width="120" Height="30" Content="Change Data Test..." Click="Button_Click" />
</StackPanel>
</Grid>
</Window>
Thank you for your time...
Ok, I found the problem...
Instead of
set { name = value; OnPropertyChanged(Name); }
set { ipAddress = value; OnPropertyChanged(IPAddress); }
I was missing the Quotesfor the String argument on method call
The correct form is
set { name = value; OnPropertyChanged("Name"); }
set { ipAddress = value; OnPropertyChanged("IPAddress"); }
Weird that the compiler didn`t throw any error.... The Method
private void OnPropertyChanged(string propertyName)
Is "Asking" for a string as input arg.
AnyWay the best to avoid these errors (that I found) is to write the event like this (The caller supplies it`s own Public Name):
private void OnPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Now I can do
set { name = value; OnPropertyChanged(); }
set { ipAddress = value; OnPropertyChanged(); }
Thank you.
I have a contentcontrol that is bound to an object (data property of DataContext in the example). Whenever the object referenced by data changes, I want to re-select the datatemplate. How can I do that?
<ContentControl Name="rootData" Content="{Binding data}"
ContentTemplateSelector="{StaticResource myTemplateSelector}"/>
In Case you are changing whole data type then your view should be as below, Only difference is removing key and using DataType for DataTemplate, this is called implicit data template
<Window x:Class="TextBindingFormatting.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TextBindingFormatting"
xmlns:viewModels="clr-namespace:TextBindingFormatting.ViewModels"
Title="MainWindow" Height="350" Width="555">
<Window.Resources>
<local:MyTemplateSelector x:Key="MyTemplateSelector"></local:MyTemplateSelector>
<DataTemplate DataType="{x:Type viewModels:Student}">
<StackPanel Orientation="Horizontal">
<Label>Id</Label>
<TextBlock Text="{Binding Id}"></TextBlock>
<Label>Name</Label>
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Parent}">
<StackPanel Orientation="Vertical">
<Label>Name</Label>
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<ContentControl Content="{Binding Data}" >
</ContentControl>
<Button Content="Change DataTemplate" Click="ButtonBase_OnClick"></Button>
</StackPanel>
</Grid>
code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel() {Data = new Student{Id = 1, Name = "Student"}};
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var vm = this.DataContext as MainWindowViewModel;
vm.Data = new Parent() {Name = "This is parent"};
}
}
I have two classes as below
public class Student : INotifyPropertyChanged
{
private string _name;
private int _id;
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public string Name {
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public int Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
}
And another
public class Parent : INotifyPropertyChanged
{
private string _name;
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
If you have same data type and different values for Data then you can use DataTemplate Selector as below, Otherwise just use DataType attribute of DataTemplate and you don't even need datatemplate selector.
Below is sample code to select template every time you change the data.
MainWindow.xaml
<Window x:Class="TextBindingFormatting.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TextBindingFormatting"
Title="MainWindow" Height="350" Width="555">
<Window.Resources>
<local:MyTemplateSelector x:Key="MyTemplateSelector"></local:MyTemplateSelector>
<DataTemplate x:Key="Template1">
<StackPanel Orientation="Horizontal">
<Label>Label 1</Label>
<Label>Label 2</Label>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="Template2">
<StackPanel Orientation="Vertical">
<Label>Label 1</Label>
<Label>Label 2</Label>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<ContentControl Content="{Binding Data}" ContentTemplateSelector="{StaticResource MyTemplateSelector}"></ContentControl>
<Button Content="Change DataTemplate" Click="ButtonBase_OnClick"></Button>
</StackPanel>
</Grid>
Below is the Code Behind, Ideally button click should be handled using command, but for quick example I have implemented in code behind just to trigger change of data.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel() {Data = "1"};
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var vm = this.DataContext as MainWindowViewModel;
vm.Data = "2";
}
}
Below is the ViewModel for MainWindow
namespace TextBindingFormatting.ViewModels
{
public class MainWindowViewModel : INotifyPropertyChanged
{
private string _data;
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public string Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged("Data");
}
}
}
}
DataTemplate Selector
namespace TextBindingFormatting
{
public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
if (element == null || item == null)
return base.SelectTemplate(item, container);
if (item.ToString() == "1")
return element.FindResource("Template1") as DataTemplate;
if (item.ToString() == "2")
return element.FindResource("Template2") as DataTemplate;
return base.SelectTemplate(item, container);
}
}
}
Found a possible solution here from Rachel:
Change Data template dynamically
DataTemplateSelector wont trigger on PropertyChange but setting triggers does the job for me!
I am having a major problem binding my data from TextBox to ViewModel To TextBlock. I have set up my following Xaml Code like so:
<Page
x:Class="digiBottle.MainPage"
DataContext="{Binding Source=UserProfile}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:digiBottle"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
DataContext="{Binding Source=UserProfile}">
<TextBlock HorizontalAlignment="Left" Margin="219,72,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="32" Width="232" Text="{Binding userFirstName, Mode=OneWay}"/>
<TextBox HorizontalAlignment="Left" Margin="39,72,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="111" Text="{Binding userFirstName, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
The .cs file I am trying to use as a source is defined as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace digiBottle.Model
{
public class UserProfile : INotifyPropertyChanged
{
public int ID { get; set; }
public string userFirstName;
public string userLastName { get; set; }
public int age { get; set; }
public int weight { get; set; }
public int height { get; set; }
public DateTime signupTime { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public UserProfile()
{
userFirstName = "First Name";
}
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public UserProfile getCopy()
{
UserProfile copy = (UserProfile)this.MemberwiseClone();
return copy;
}
}
}
What am i doing wrong when trying to bind my TextBox and TextBlock to userFirstName in the UserProfile.cs Source. ANy help would be a major help!
Thank you
The first thing I notice here is that your properties (setter) are not raising event change. You need to call RaisePropertyChanged in your properties setter.
I would have written it like
A private field
private String _userFirstName;
Then in constructor
public UserProfile()
{
this._userFirstName = "First Name";
}
With Property raising event
public String UserFirstName
{
get { return this._userFirstName; }
set
{
this._userFirstName = value;
this.RaisePropertyChanged("UserFirstName");
}
}
And then in XAML, bind it with property "UserFirstName" with two way binding
<TextBlock HorizontalAlignment="Left" Margin="219,72,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="32" Width="232" Text="{Binding UserFirstName, Mode=OneWay}"/>
<TextBox HorizontalAlignment="Left" Margin="39,72,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="111" Text="{Binding UserFirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
DataBinding can be hard to understand at first. Please refer to Data binding for Windows Phone 8 to get yourself started.
For your code: Here are the fixes you will need:
Remember you can only bind to a property.
You need to raise the event on the set action.
You may need twoway binding on the textbox depending on the actions you want.
You need to set the DataContext for both textbox and the textblock.
Here are the changes:
CS
public class UserProfile : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string user_first_name;
public String UserFirstName
{
get { return user_first_name; }
set
{
user_first_name = value;
OnPropertyChanged("UserFirstName");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
UserProfile up = new UserProfile();
this.tb1.DataContext = up;
this.tb2.DataContext = up;
}
}
XAML
<TextBlock x:Name="tb2" TextWrapping="Wrap" Text="{Binding UserFirstName}"/>
<TextBox x:Name="tb1" HorizontalAlignment="Left" Height="72" Margin="14,475,0,0" Grid.Row="1" TextWrapping="Wrap" Text="{Binding UserFirstName, Mode=TwoWay}" VerticalAlignment="Top" Width="456" />