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;
}
Related
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.
I have a textbox inside of a listbox that I would like to update the ObservableCollection when the textbox loses focus. I tried using my collections CollectionChanged event as described in this post here to try to solve the problem. Right now the only way to update the collection is if I add or remove an item from the listbox. Am I going about this the wrong way? What am I missing for the textbox to update the collection?
MainWindow.xaml
<ListBox ItemsSource="{Binding DataLogList,Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding DataLogLabel}" Margin="5"/>
<TextBox Text="{Binding DataLogName,Mode=TwoWay,UpdateSourceTrigger=LostFocus}" Margin="5" Width="150"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
MainViewModel.cs
public MainViewModel()
{
DataLogList = new ObservableCollection<DataLogContent>();
DataLogList.CollectionChanged += (s, e) =>
{
if (e.NewItems != null)
{
foreach (DataLogContent item in e.NewItems)
{
item.PropertyChanged += item_PropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (DataLogContent item in e.OldItems)
{
item.PropertyChanged -= item_PropertyChanged;
}
}
};
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged();
}
DataLogContent.cs
public class DataLogContent:ViewModelBase
{
private string dataLogLabel;
public string DataLogLabel
{
get { return this.dataLogLabel; }
set
{
this.dataLogLabel = value;
NotifyPropertyChanged();
}
}
private string dataLogName;
public string DataLogName
{
get { return this.dataLogName; }
set
{
this.dataLogLabel = value;
NotifyPropertyChanged();
}
}
}
I have it working based on this. My guess is that you may be over complicating the adding/removing logic of an item inside the ObservableCollection. There's no need to monitor the property changed event, as each object will already raise the event whenever a property within it changes.
Here's what I have:
namespace WpfApplication1
{
public class MainViewModel : ViewModelBase
{
public ObservableCollection<DataLogContent> DataLogList { get; private set; }
public MainViewModel()
{
DataLogList = new ObservableCollection<DataLogContent>();
DataLogList.Add(new DataLogContent
{
DataLogLabel = "Label",
DataLogName = "Name"
});
DataLogList.Add(new DataLogContent
{
DataLogLabel = "Label2",
DataLogName = "Name2"
});
}
}
public class DataLogContent : ViewModelBase
{
private string dataLogLabel;
public string DataLogLabel
{
get { return this.dataLogLabel; }
set
{
this.dataLogLabel = value;
OnPropertyChanged("DataLogLabel");
}
}
private string dataLogName;
public string DataLogName
{
get { return this.dataLogName; }
set
{
this.dataLogName = value;
OnPropertyChanged("DataLogName");
}
}
}
}
Simple ViewModelBase:
namespace WpfApplication1
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(property));
}
}
}
}
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding DataLogList,Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding DataLogLabel}" Margin="5"/>
<TextBox Text="{Binding DataLogName,Mode=TwoWay,UpdateSourceTrigger=LostFocus}" Margin="5" Width="150"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
I have used many ComboBoxes in my applications and all of them are working without any problem. But, I can't find the problem now. I have set SelectedValuePath to "Tag" property. But the property not updating after changing the ComboBox selected item. I have read other StackOverflow questions, but nontheless helped.
It is xaml:
xmlns:vms="clr-namespace:SilverlightApplication1"
<UserControl.DataContext>
<vms:MainViewModel />
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox Width="100" VerticalAlignment="Center" FontFamily="Segoe UI"
Height="30" Margin="0,5,0,0" HorizontalAlignment="Left"
SelectedValue="{Binding SelectedDifStatusComparer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Tag">
<ComboBox.Items>
<ComboBoxItem Tag="H" >High</ComboBoxItem>
<ComboBoxItem Tag="L" >Low</ComboBoxItem>
<ComboBoxItem Tag="E" >Equal</ComboBoxItem>
</ComboBox.Items>
</ComboBox>
</Grid>
And here is the ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _selectedDifStatusComparer = "";
private string SelectedDifStatusComparer
{
get { return _selectedDifStatusComparer; }
set
{
_selectedDifStatusComparer = value;
MessageBox.Show(_selectedDifStatusComparer);
OnPropertyChanged("SelectedDifStatusComparer");
}
}
public MainViewModel()
{
SelectedDifStatusComparer = "E"; // It is working, the MessageBox is apperaing
}
}
Your property is private. Change it to public and it should work.
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _selectedDifStatusComparer = "";
public string SelectedDifStatusComparer
{
get { return _selectedDifStatusComparer; }
set
{
_selectedDifStatusComparer = value;
MessageBox.Show(_selectedDifStatusComparer);
OnPropertyChanged("SelectedDifStatusComparer");
}
}
public MainViewModel()
{
SelectedDifStatusComparer = "E"; // It is working, the MessageBox is apperaing
}
}
Your property is private. Change it to public and it should work.
I'm trying to validate the text in a textbox when a key is pressed. Here's the shortest code sample that shows what I'm trying to do:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox FontSize="15" HorizontalAlignment="Left" Name="txtEmail" VerticalAlignment="Top" Width="135"
Text="{Binding ValidationRules.EmailAddress, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
"ValidationRules" class:
class ValidationRules
{
string email = "";
public string EmailAddress
{
get
{
return email;
}
set
{
Console.WriteLine("Setting!");
//Only check if there is any text for now...
if (String.IsNullOrWhiteSpace(value))
{
throw new Exception();
}
email = value;
}
}
}
When I start typing in the textbox, I don't get "Setting" as console output, even though I'm using UpdateSourceTrigger=PropertyChanged. I've done my research but all the examples I could find are long winding and confusing. I would also appreciate it if you could point out any other mistakes I have in validation, but try to explain in simple terms if possible because I'm new to WPF.
It must be an issue on where you are setting your DataContext.
This example seems to work fine:
Code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
ValidationRules = new ValidationRules();
}
private ValidationRules _validation;
public ValidationRules ValidationRules
{
get { return _validation; }
set { _validation = value; NotifyPropertyChanged("ValidationRules"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class ValidationRules : INotifyPropertyChanged
{
string email = "";
public string EmailAddress
{
get
{
return email;
}
set
{
Console.WriteLine("Setting!");
//Only check if there is any text for now...
if (String.IsNullOrWhiteSpace(value))
{
throw new Exception();
}
email = value;
NotifyPropertyChanged("EmailAddress");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Xaml
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="125.078" Width="236.441" x:Name="UI" >
<Grid DataContext="{Binding ElementName=UI}">
<TextBox FontSize="15" HorizontalAlignment="Left" Name="txtEmail" VerticalAlignment="Top" Width="135"
Text="{Binding ValidationRules.EmailAddress, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
I have a MVVM setup that creates a View on my MainWindow. I am not sure how to know when a user Clicks on a specific Notification Item inside the View. Where would I add the event, or a command to know when that happens?
here are is my MVVM code :
MainWindow
cs:
NotificationViewModel notificationViewModel = new NotificationViewModel();
notificationViewModel.AddNoticiation(new NotificationModel() { Message = "Error", Name = "Station 21" });
NotificationView.DataContext = notificationViewModel;
xaml:
<notification:NotificationView x:Name="NotificationView" />
NotificationModel
public class NotificationModel : INotifyPropertyChanged
{
private string _Message;
public string Message
{
get { return _Message; }
set
{
if (_Message != value)
{
_Message = value;
RaisePropertyChanged("Message");
}
}
}
private string _Name;
public string Name
{
get { return _Name; }
set
{
if (_Name != value)
{
_Name = value;
RaisePropertyChanged("Name");
}
}
}
public string TimeStamp
{
get { return DateTime.Now.ToString("h:mm:ss"); }
}
#region PropertChanged Block
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
#endregion
}
NotificationViewModel
public class NotificationViewModel
{
private ObservableCollection<NotificationModel> _Notifications = new ObservableCollection<NotificationModel>();
public ObservableCollection<NotificationModel> Notifications
{
get { return _Notifications; }
set { _Notifications = value; }
}
public void AddNoticiation(NotificationModel notification)
{
this.Notifications.Insert(0, notification);
}
}
NotificationView
<Grid>
<StackPanel HorizontalAlignment="Left" >
<ItemsControl ItemsSource="{Binding Path=Notifications}"
Padding="5,5,5,5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="SlateGray"
CornerRadius="4">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding Path=TimeStamp}" />
<TextBlock Grid.Column="1"
Text="{Binding Path=Name}" />
<TextBlock Grid.Column="2"
Text="{Binding Path=Message}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
There's no real selection mechanism built into an ItemsControl. It would probably solve your problem to switch out your ItemsControl for a ListBox.
If you do that, you can bind to SelectedItem, then handle any changes made to SelectedItem using the PropertyChanged event.
Example:
In your view model's constructor:
PropertyChanged += NotificationViewModel_PropertyChanged;
Add a property to your view model to allow the binding:
private string _selectedNotification;
public string SelectedNotification
{
get { return _selectedNotification; }
set
{
if (_selectedNotification != value)
{
_selectedNotification = value;
RaisePropertyChanged("SelectedNotification");
}
}
}
Finally, add the event handler to your view model:
NotificationViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e))
{
if (e.PropertyName = "SelectedNotification") DoStuff();
}
You may find that you don't even need to hook into PropertyChanged if you just want to update another control in your view based on the selected item in your list box. You can just bind directly to the property within xaml.