WPF: DataTemplate and UserControl map not working - c#

I'm starting a WPF project. Trying to binding usercontrol by viewmodel. Where viewmodel define with dataType at DataTemplate in Application.Reousrces. But user control not bind. Any one can help me?
<Application.Resources>
<DataTemplate DataType="{x:Type vm:MatterPanelViewModel}">
<uc:MatterPanel />
</DataTemplate>
</Application.Resources>
Main Window where will bind user control.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:MyProject"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="MyProject.MainWindow"
Title="MyProject" WindowState="Maximized" d:DataContext="{d:DesignInstance Type=uc:MainWindowViewModel}">
<Grid Grid.Row="2">
<ContentControl Content="{Binding CurrentViewModel}" Margin="10,0,10,10" />
</Grid>
</Window>
CurrentViewModel is the property of MainViewModel.
public class MainWindowViewModel:ViewModelBase
{
private ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel
{
get { return this._currentViewModel; }
set
{
if(this._currentViewModel == value) { return; }
this._currentViewModel = value;
this.NotifyOfPropertyChange(() => this.CurrentViewModel);
}
}
public MatterPanelViewModel MatterPanelViewModel { get; set; }
public MainWindowViewModel()
{
this.MatterPanelViewModel = ServiceLocator.Current.GetService<MatterPanelViewModel>();
}
}
public class MatterPanelViewModel:ViewModelBase
{
public MatterPanelViewModel()
{
}
}
ViewModelBase here,
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged
{
add { this._propertyChanged += value; }
remove { this._propertyChanged -= value; }
}
private event PropertyChangedEventHandler _propertyChanged = delegate{ };
protected void NotifyOfPropertyChange<T>(Expression<Func<T>> property)
{
var lambda = (LambdaExpression)property;
MemberExpression memberExpression;
if(lambda.Body is UnaryExpression)
{
var unaryExpression = (UnaryExpression)lambda.Body;
memberExpression = (MemberExpression)unaryExpression.Operand;
}
else
{
memberExpression = (MemberExpression)lambda.Body;
}
this.NotifyOfPropertyChange(memberExpression.Member.Name);
}
public void NotifyOfPropertyChange(string property)
{
this.RaisePropertyChanged(property, true);
}
private void RaisePropertyChanged(string property, bool verifyProperty)
{
var handler = this._propertyChanged;
if(handler != null)
{
handler(this, new PropertyChangedEventArgs(property));
}
}
}

Finally I solve the problem. The viewmodel and usercontrol map should under the MainWindow but here under main app. I just code from main app
<Application.Resources>
<DataTemplate DataType="{x:Type vm:MatterPanelViewModel}">
<uc:MatterPanel />
</DataTemplate>
</Application.Resources>
to Main Window
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<DataTemplate DataType="{x:Type vm:MatterPanelViewModel}">
<usc:MatterPanel/>
</DataTemplate>
</Window.Resources>
Then its working well.

Related

WPF Command Not Executing

My main window is currently displaying the YearView in a content control via DataTemplated YearViewModel. When I click the month button on the YearView I want the main window to instead display the MonthView. The MainViewModel (view model of the main window not shown) retrieves the view model to be displayed from '_navigationStorage.CurrentViewModel' as seen in NavigateMonthCommand.Execute(). But when I click the month button, NavigateMonthCommand.Execute() is never called.
Is the month button binding not working? In YearView.xaml should I be specifying the DataContext differently?
MainWindow.xaml
<Window x:Class="Calandar.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:Calandar.ViewModels"
xmlns:views="clr-namespace:Calandar.Views"
xmlns:local="clr-namespace:Calandar"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Background="LightSlateGray">
<ContentControl Content="{Binding CurrentViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewmodels:YearViewModel}">
<views:YearView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:MonthViewModel}">
<views:MonthView/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</Window>
YearView.xaml
<UserControl x:Class="Calandar.Views.YearView"
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:Calandar.Views"
xmlns:viewmodels="clr-namespace:Calandar.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="White" DataContext="viewmodels:YearViewModel">
<StackPanel>
<TextBlock Text="Year" FontSize="55"/>
<Button Content="Month" Command="{Binding NavigateMonthCommand}"/>
</StackPanel>
</Grid>
</UserControl>
YearViewModel.cs
public class YearViewModel : ViewModelBase
{
public ICommand NavigateMonthCommand { get; }
public YearViewModel(NavigationStorage navigationStorage)
{
NavigateMonthCommand = new NavigateMonthCommand(navigationStorage);
}
}
NavigateMonthCommand.cs
public class NavigateMonthCommand : CommandBase
{
private readonly NavigationStorage _navigationStorage;
public NavigateMonthCommand(NavigationStorage navigationStorage)
{
_navigationStorage = navigationStorage;
}
public override void Execute(object parameter)
{
;
_navigationStorage.CurrentViewModel = new MonthViewModel();
}
}
CommandBase.cs
public abstract class CommandBase : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public abstract void Execute(object parameter);
public void OnCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, new EventArgs());
}
}
Look this one
Without iNotifyPropertyChanged Viewmodel cannot get data from view interface. You must connect properly. You should derive ViewModelBase from INotifyPropertyChanged.
I think you have created your BaseViewModel, you can create the method there.
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string PropertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
you need to call it while doing property set operation in the YearViewModel you specified. Sample:
private string surName;
public string SurName
{
get { return surName; }
set { surName = value; OnPropertyChanged(nameof(SurName)); }
}
Good Luck

Binding textbox without static class

I have a problem to bind a subclass to my XML textbox, I followed this post to do it, but it does not work without using a static class. Is there a way to do without using a static class?
I followed this post as reference.
Binding textbox values to a model in wpf
My code is:
public class Model:INotifyPropertyChanged{
public event PropertyChangedEventHandler PropertyChanged;
private string title;
public string Title{
get {
return title;
}
set {
if (tilte!= value) {
tilte= value;
NotifyPropertyChanged();
}
}
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") {
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class ViewModel{
public Model modelObj;
public ViewModel(){
modelObj= new Model();
this.DataContext = modelObj;
modelObj.Title = "New title"; // <--- this don't update xml
}
}
<Page
x:Class="AppTest.Demo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AppTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:m ="using:Models"
xmlns:vm ="using:ViewModels"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.DataContext>
<m:Model></m:Model>
</Page.DataContext>
<Grid>
<TextBlock Text="{Binding Path=Title}"/>
</Grid>
</Page>
You could set your view model as the data context and bind to Model.Title.
Update
This works:
<Page x:Class="WpfApplication8.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AppTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:m ="using:Models"
xmlns:vm="clr-namespace:WpfApplication8.ViewModels">
<Page.DataContext>
<vm:ViewModel/>
</Page.DataContext>
<Grid>
<TextBlock Text="{Binding ModelObj.Title, TargetNullValue='null', FallbackValue='fallback'}"/>
</Grid>
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Model : BindableBase
{
private string title;
public string Title
{
get
{
return title;
}
set
{
if (title != value)
{
title = value;
NotifyPropertyChanged();
}
}
}
}
public class ViewModel : BindableBase
{
private Model modelObj;
public Model ModelObj
{
get
{
return modelObj;
}
set
{
modelObj = value;
NotifyPropertyChanged();
}
}
public ViewModel()
{
ModelObj = new Model();
ModelObj.Title = "New title";
}
}
You should set the Title property of the instance of the Model class that you set as the DataContext of the Page:
<Page.DataContext>
<m:Model Title="New title"></m:Model>
</Page.DataContext>
Or:
<Page.DataContext>
<m:ViewModel />
</Page.DataContext>
<Grid>
<TextBlock Text="{Binding Path=modelObj.Title}"/>
</Grid>
Also, you don't set the DataContext property of a view model. You set the DataContext property of a view to an instance of a view model.
Edit:
modelObj must be a public property (and not a field) in order for you to be able to bind to it:
public Model modelObj { get; set; }

How to establish a viewModel to a ContentControl

I've got a usercontrol which defines a ContentControl like this:
<ContentControl x:Name="PART_contentHost" Grid.Row="1"/>
In the viewmodel I will get a viewModel which will be displayed inside the contentControl. To establish the link with the view I have a datatemplate that establish the relationship between both of them.
<DataTemplate DataType="{x:Type ViewModels:Test1ViewModel}">
<Views:Test1View />
</DataTemplate>
This means that I want Test1ViewModel to be shown inside the contentControl. I am not able to stablish that in my code C#.
//this gets the contentControl from de template
contentHost = this.Template.FindName(contentHostName, this) as ContentControl;
//this assigns the test1ViewModel
contentHost.Content = content
What am I missing?
You have not shared enough code for me to be sure what you are trying to do. While there are cases in which you will need to parse templates, most often there is a better way. So here is how I understand your case in a MVVM context, can you do it this way?
Xaml:
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:Test1ViewModel}">
<local:Test1View />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding ContentModel}" />
</Grid>
Test1View:
<UserControl x:Class="WpfApplication1.Test1View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBlock Text="{Binding Name}" Background="Beige" Padding="5" />
<TextBlock Text="{Binding Address}" Background="PeachPuff" Padding="5" />
</StackPanel>
</UserControl>
ViewModels:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private Test1ViewModel _contentModel;
public Test1ViewModel ContentModel { get { return _contentModel; } set { _contentModel = value; OnPropertyChanged("ContentModel"); } }
public ViewModel()
{
this.ContentModel = new Test1ViewModel() { Name = "John Higgins", Address = "Wishaw" };
}
}
public class Test1ViewModel : INotifyPropertyChanged
{
private string _name;
public string Name { get { return _name; } set { _name = value; OnPropertyChanged("Name"); } }
private string _address;
public string Address { get { return _address; } set { _address = value; OnPropertyChanged("Address"); } }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I have done something of that nature before.
This code should get you started.
public void FindAndSetTemplateContent( ContentControl target, ViewModelBase item)
{
if (target == null)
throw new ArgumentNullException("target");
if (item == null)
throw new ArgumentNullException("item");
var template = target.TryFindResource(new DataTemplateKey(item.GetType())) as DataTemplate; // this will pick up your resource for the viewmodel
if (template == null)
return null;
var content = template.LoadContent() as ContentControl ;
if (content != null)
{
content.DataContext = item;
}
return content;
}

Binding error with ListView, UserControl, DependencyProperty, ObservableCollection, INotifyPropertyChanged

My binding does not work. I searched for the error but I don't understand how to fix it in my case.
System.Windows.Data Error: 1 : Cannot create default converter to perform 'one-way' conversions between types 'MyApplication.MyUserControl' and 'MyApplication.Person'. Consider using Converter property of Binding. BindingExpression:Path=; DataItem='MyUserControl' (Name=''); target element is 'MyUserControl' (Name=''); target property is 'PersonInfo' (type 'Person')
System.Windows.Data Error: 5 : Value produced by BindingExpression is not valid for target property.; Value='MyApplication.MyUserControl' BindingExpression:Path=; DataItem='MyUserControl' (Name=''); target element is 'MyUserControl' (Name=''); target property is 'PersonInfo' (type 'Person')
Basically it's a ListView that is bound to a ObservableCollection of the class Person.
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public ObservableCollection<Person> PersonCollection { set; get; }
public MainWindow()
{
PersonCollection = new ObservableCollection<Person>();
InitializeComponent();
PersonCollection.Add(new Person() { Name = "Bob", Age = 20 });
}
}
MainWindow.xaml
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}" xmlns:self="clr-namespace:MyApplication" x:Class="MyApplication.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ListView ItemsSource="{Binding PersonCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<self:MyUserControl PersonInfo="{Binding}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Window>
MyUserControl.xaml.cs
public partial class MyUserControl : UserControl
{
public static readonly DependencyProperty PersonProperty = DependencyProperty.Register("PersonInfo", typeof(Person), typeof(MyUserControl));
public Person PersonInfo
{
get { return (Person)GetValue(PersonProperty); }
set { SetValue(PersonProperty, value); }
}
public MyUserControl()
{
InitializeComponent();
}
}
MyUserControl.xaml
<UserControl DataContext="{Binding RelativeSource={RelativeSource Self}}" x:Class="MyApplication.MyUserControl" 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">
<TextBlock Text="{Binding PersonInfo.Name}" />
</UserControl>
Person.cs
public class Person : INotifyPropertyChanged
{
public int Age { set; get; }
public string Name { set; get; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
I can't quite understand why you would make it that complicated. You could easily bind your UserControl without the PersonInfo property and without modifying its DataContext.
<UserControl x:Class="MyApplication.MyUserControl" 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">
<TextBlock Text="{Binding Name}" />
</UserControl>
Then place the UserControl in a DataTemplate without an explicit binding. Its DataContext will then already contain a Person object.
<DataTemplate>
<StackPanel>
<self:MyUserControl />
</StackPanel>
</DataTemplate>
Although you fixed your problem, your entire Binding code seems wrong to me, so I propose this alternative:
Have a base class for all binding source objects - ObservableObject.cs
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void SetValue<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
}
Have a view model for your MainWindow - MainWindowModel.cs
public class MainWindowModel : ObservableObject
{
private readonly ObservableCollection<Person> personCollection = new ObservableCollection<Person>()
{
new Person() { Name = "Bob", Age = 20 }
};
public ObservableCollection<Person> PersonCollection
{
get { return this.personCollection; }
}
}
MainWindow.xaml.cs is now basically empty.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
MainWindow.xaml sets DataContext to new MainWindowModel instance.
<Window x:Class="MyApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clr-namespace:MyApplication">
<Window.DataContext>
<self:MainWindowModel/>
</Window.DataContext>
<ListView ItemsSource="{Binding PersonCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<self:MyUserControl/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Window>
MyUserControl.xaml.cs is also basically empty (contains only auto-generated code).
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
}
MyUserControl.xaml
<UserControl x:Class="MyApplication.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock Text="{Binding Name}"/>
</UserControl>
Person.cs
public class Person : ObservableObject
{
private int age;
private string name;
public int Age
{
get { return this.age; }
set { this.SetValue(ref this.age, value, "Age"); }
}
public string Name
{
get { return this.name; }
set { this.SetValue(ref this.name, value, "Name"); }
}
}
Change your UserControl XAML to
<UserControl x:Class="MyApplication.MyUserControl"
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">
<TextBlock DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"
Text="{Binding PersonInfo.Name}" />
</UserControl>
Here is a good explanation for the problem with DataContext.

Problem SilverLight 4 DataPager control

I want to bind DataPager to DataGrid
here is xaml
<UserControl xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="NorthWindSilver.MainPage"
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:mv="clr-namespace:NorthWindSilver.ViewModel"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<mv:ViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<data:DataGrid Name="dgCustomer" AutoGenerateColumns="True" ItemsSource="{Binding Items, Mode=TwoWay, Source={StaticResource ViewModel}}">
</data:DataGrid>
<sdk:DataPager HorizontalContentAlignment="Center" x:Name="myPager" Grid.Row="2" Source="{Binding Path=ItemsSource, ElementName=dgCustomer}" PageSize="10"/>
</Grid>
and ViewModel
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Customer> _items;
public ViewModel()
{
if (!IsDesignTime)
this.Customer();
}
public void ChangeProperty(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ObservableCollection<Customer> Items
{
get
{
return this._items;
}
set
{
this._items = value;
ChangeProperty("Items");
}
}
public bool IsDesignTime
{
get
{
return (Application.Current == null) ||
(Application.Current.GetType() == typeof(Application));
}
}
public void Customer()
{
DataServiceClient webService = new DataServiceClient();
webService.GetCustomersCompleted += new EventHandler<GetCustomersCompletedEventArgs>(webService_GetCustomersCompleted);
webService.GetCustomersAsync();
}
void webService_GetCustomersCompleted(object sender, GetCustomersCompletedEventArgs e)
{
Items = e.Result;
PagedCollectionView pageView = new PagedCollectionView(Items);
MainPage ma = new MainPage();
ma.dgCustomer.ItemsSource = pageView;
}
}
Here is the result
As you see DataPager does not work
what the problem?
Try to expose your PagedCollectionView as a property on the ViewModel and bind the ItemsSource on the DataGrid and Pager to the PagedCollectionView instead.

Categories

Resources