I made a simple example to better understan the MVVM pattern.
Here is a link to the sample solution, because its difficult to explain the whole problem:
http://www.2shared.com/file/jOOAnacd/MVVMTestMyCopy.html
There is Employee model (with Age property) and EmployeeViewModel, which contains Employee object and changes its Age property in the following code:
public int Age
{
get { return _employee.Age; }
set
{
if (value == _employee.Age)
return;
_employee.Age = value;
NotifyPropertyChanged("Age");
}
}
EmployeeViewModel is inherited from ViewModelBase class with standard INotifyPropertyCHanged code:
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
I'm trying to change employee's age using ICommand:
public void Increase()
{
this.SelectedEmployee.Age++;
NotifyPropertyChanged("Age");
}
The property is changed, but the binded TextBLock does not change its value.
I checked and saw that NotifyPropertyChanged is called, but PropertyChanged is null.
I also ensured that I have only one PeopleViewModel in my app.
So, why is the PropertyChanged is null?
EDIT:
Here is full code for ViewModelBase:
public class ViewModelBase
{
public String DisplayName { get; set; }
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
#endregion
}
There is PeopleViewModel which contains ObservalbleCollection with EmployeeViewModels and set as DataContext.
The values of properties are changed, but the changes are not shown without reloading objects.
Here is the PeopleViewer.xaml that shows the binding:
<UserControl x:Class="MVVMTestMyCopy.View.PeopleViewer"
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:vm="clr-namespace:MVVMTestMyCopy.ViewModel"
mc:Ignorable="d"
d:DesignHeight="316" d:DesignWidth="410">
<UserControl.Resources>
<vm:PeopleViewModel x:Key="viewModel"/>
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource viewModel}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding People}"
Grid.Column="0"
Margin="5,5,4,5"
SelectedItem="{Binding SelectedEmployee, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="2"
Text="{Binding FirstName}" />
<TextBlock Margin="2"
Text="{Binding LastName}" />
<TextBlock Margin="0 2"
Text="[" />
<TextBlock Margin="2"
Text="{Binding Age}" />
<TextBlock Margin="0 2"
Text="]" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="0.75*" />
<RowDefinition Height="0.25*" />
</Grid.RowDefinitions>
<Grid x:Name="EmployeeDetails"
Grid.Row="0"
DataContext="{Binding SelectedEmployee}"
Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding FirstName}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Grid.Column="0"/>
<TextBlock Text="{Binding LastName}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Grid.Row="1" />
<TextBlock Text="{Binding Age}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Grid.Row="2" />
</Grid>
<StackPanel Orientation="Vertical"
HorizontalAlignment="Center"
Grid.Row="1">
<Button x:Name="button"
Content="-"
Width="32"
Height="32"
Command="{Binding DecreaseCommand}">
</Button>
<Button x:Name="button1"
Content="+"
Width="32"
Height="32"
Command="{Binding IncreaseCommand}">
</Button>
</StackPanel>
</Grid>
</Grid>
</UserControl>
In your project, you don't actually implement INotifyPropertyChanged on your view-model. You have:
public class ViewModelBase
But this should be:
public class ViewModelBase : INotifyPropertyChanged
Because you don't implement INotifyPropertyChange, the WPF binding system will not be able to add a handler for your PropertyChanged event.
Implement the INotifyPropertyChanged interface on your ViewModelBase.
http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx
You have defined a PropertyChanged event but it is the interface that is important.
I checked you application using MVVM-Light instead of your BaseViewModel implementation and it worked as it should.
I suggest using MVVM-Light because of other features like Messaging, Disposing and Blendability.
You can easily download and install it using NuGet.
If you want to implement INotifyPropertyChange anyway, here is code that will do it:
public class MainViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
Related
I have the following database http://merc.tv/img/fig/Northwind_diagram.jpg and I'm making a WPF application that when I click on an employee, it shows me the orders done by that employee. Whenever I run the code, I get a NullReferenceException at this part:
public List<Order> orders {
get { return selEmp.Orders.ToList(); }
}
This is my WPF code:
<Window x:Class="_ForPLUENorthwind1.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:local="clr-namespace:_ForPLUENorthwind1"
xmlns:localn="clr-namespace:_ForPLUENorthwind1.model"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ObjectDataProvider x:Key="vm" ObjectType="{x:Type localn:viewmodel}" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource vm}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<ListBox x:Name="liemp" Grid.Column="0" Grid.Row="0"
ItemsSource="{Binding Allemp}"
DisplayMemberPath="FirstName"
SelectedValuePath="EmployeeID"
SelectedItem="{Binding Path=selEmp, Mode=TwoWay}"
/>
<ListBox x:Name="liorders" Grid.Column="1" Grid.Row="0" ItemsSource="{Binding orders}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding OrderID}" />
<Run Text="{Binding OrderDate}" />
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="2" Grid.Row="0">
<StackPanel>
<TextBlock Text="" />
<TextBox />
<TextBlock Text="" />
<TextBox />
<TextBlock Text="" />
<TextBox />
</StackPanel>
<Button x:Name="edit">Edit</Button>
<Button x:Name="add" >Add</Button>
</StackPanel>
</Grid>
</Window>
And this is my viewmodel.cs:
class viewmodel : INotifyPropertyChanged
{
NorthwindEntities db = new NorthwindEntities();
public event PropertyChangedEventHandler PropertyChanged;
private Employee _emp;
public List<Employee> Allemp {
get { return db.Employees.ToList(); }
}
public Employee selEmp {
get { return _emp; }
set {
_emp = value;
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs("orders"));
}
}
}
public List<Order> orders {
get { return selEmp.Orders.ToList(); }
}
}
The second ListBox should contain the Orders
Update: When I go through the software using debug mode, it runs and shows all the orders, but I still get the null reference
First of all, use this for setter:
set {
_emp = value;
NotifyPropertyChange();
if _emp != null
NotifyPropertyChange("orders");
}
And the NotifyPropertyChange should be
private void NotifyPropertyChange([CallerMemberName]string prop = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
And change the orders to:
public List<Order> orders {
get { return (selEmp.Orders==null? null : selEmp.Orders.ToList()); }
}
See if you getting NullReferenceException at this point. Even then, you might have problems with the binding. You should use ObservableCollection for binding, not List.
I use .NETFramework,Version=v4.6.1
I have a Window, MainWindow. This is the XAML:
<Window x:Class="VexLibrary.DesktopClient.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VexLibrary.DesktopClient.Views"
Title="MainWindow" Height="600" Width="800">
<Grid>
<StackPanel>
<Grid Style="{StaticResource TitleBar}">
<Border Style="{StaticResource TitleBarBorder}">
<DockPanel>
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal">
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
<Label Style="{StaticResource TitleBarTitle}" Content="{Binding Path=CurrentPageTitle, UpdateSourceTrigger=PropertyChanged}" ></Label>
</StackPanel>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
<Label Style="{StaticResource TitleBarTime}">12:05 AM</Label>
<StackPanel Orientation="Horizontal">
<Label Style="{StaticResource TitleBarUsername}">Hassan</Label>
<Button>
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
</Button>
</StackPanel>
</StackPanel>
</DockPanel>
</Border>
</Grid>
<Frame Width="700" Height="507" Source="Pages/Dashboard.xaml" />
</StackPanel>
</Grid>
</Window>
Note the:
<Label Style="{StaticResource TitleBarTitle}" Content="{Binding Path=CurrentPageTitle, UpdateSourceTrigger=PropertyChanged}" ></Label>
The DataContext is set as follows in the MainWindow.xaml.cs constructor:
this.DataContext = new MainViewModel();
In the <Frame>, a Page Dashboard.xamlis loaded.
The page Dashboard.xaml has the source:
<Page x:Class="VexLibrary.DesktopClient.Views.Pages.Dashboard"
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:VexLibrary.DesktopClient.Views.Pages"
mc:Ignorable="d"
d:DesignHeight="460" d:DesignWidth="690"
Title="Page1">
<Grid Width="690" Height="460" HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- Members, Users, Books -->
<!-- Returns, Subscriptions, Statistics -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Button Style="{StaticResource MenuButton}" Grid.Column="0" Grid.Row="0"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="0" Grid.Row="1"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="1" Grid.Row="0"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="1" Grid.Row="1"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="2" Grid.Row="0"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="2" Grid.Row="1" Command="{Binding ViewStatistics}"></Button>
</Grid>
</Page>
In the Dashboard.xaml.cs constructor, I have defined the DataContext like this: DataContext = new DashboardViewModel();
The DashboardViewModel.cs source code is like this (omitted namespaces)
namespace VexLibrary.DesktopClient.ViewModels
{
class DashboardViewModel : ViewModel
{
private MainViewModel parentViewModel;
public DashboardViewModel()
{
this.parentViewModel = new MainViewModel();
}
public ICommand ViewStatistics
{
get
{
return new ActionCommand(p => this.parentViewModel.LoadPage("Statistics"));
}
}
}
}
Now, in this code, notice the Button with the Command:
<Button Style="{StaticResource MenuButton}" Grid.Column="2" Grid.Row="1" Command="{Binding ViewStatistics}"></Button>
It successfully calls the Command and the parent LoadPage method is executed correctly. The parent viewmodel looks like this:
namespace VexLibrary.DesktopClient.ViewModels
{
public class MainViewModel : ViewModel
{
private string currentPageTitle;
public string CurrentPageTitle
{
get
{
return this.currentPageTitle;
}
set
{
currentPageTitle = value;
NotifyPropertyChanged();
}
}
public void LoadPage(string pageName)
{
this.CurrentPageTitle = pageName;
Console.WriteLine(CurrentPageTitle);
}
}
}
The CurrentPageTitle is successfully updated. However, it is not updated in the view.
The parent view model inherits ViewModel which basically has this code:
namespace VexLibrary.Windows
{
public abstract class ViewModel : ObservableObject, IDataErrorInfo
{
public string this[string columnName]
{
get
{
return OnValidate(columnName);
}
}
[Obsolete]
public string Error
{
get
{
throw new NotImplementedException();
}
}
protected virtual string OnValidate(string propertyName)
{
var context = new ValidationContext(this)
{
MemberName = propertyName
};
var results = new Collection<ValidationResult>();
bool isValid = Validator.TryValidateObject(this, context, results, true);
if (!isValid)
{
ValidationResult result = results.SingleOrDefault(p =>
p.MemberNames.Any(memberName =>
memberName == propertyName));
return result == null ? null : result.ErrorMessage;
}
return null;
}
}
}
ObservableObject.cs:
namespace VexLibrary.Windows
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// [CallerMemberName] automatically resolves the property name for us.
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
Console.WriteLine(handler == null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
After debugging, I found out, the NotifyPropertyChanged is invoked, but the handler is always null. How do I fix this? This is not updating the text in the MainWindow.xaml. I tested to see if the property value is changed, and yes, it is changed in the MainViewModel.cs
Also, I tested whether the label itself is visible or not. For that, I gave the variable a value and it correctly displays, but it is not updated.
The DashboardViewModel is instantiating a new instance of the MainViewModel rather than using the instance assigned to the DataContext of the MainWindow (and therefore the instance the view is bound to).
For your code to work you need to pass the correct instance of the MainViewModel to the DashboardViewModel as it is this instance that will have a handler for the property changed event.
EDIT: As per the comment below, you should instantiate your sub viewmodels as follows:
namespace VexLibrary.DesktopClient.ViewModels
{
public class MainViewModel : ViewModel
{
private ViewModel _currentViewModel;
public MainViewModel()
{
_currentViewModel = new DashboardViewModel(this);
}
public ViewModel CurrentViewModel
{
get { return _currentViewModel; }
private set
{
_currentViewModel = value;
OnPropertyChanged();
}
}
}
}
You can then amend your Xaml such that the frame gets it's data context from the CurrentViewModel property as follows:
<Window x:Class="VexLibrary.DesktopClient.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VexLibrary.DesktopClient.Views"
Title="MainWindow" Height="600" Width="800">
<Grid>
<StackPanel>
<Grid Style="{StaticResource TitleBar}">
<Border Style="{StaticResource TitleBarBorder}">
<DockPanel>
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal">
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
<Label Style="{StaticResource TitleBarTitle}" Content="{Binding Path=CurrentPageTitle, UpdateSourceTrigger=PropertyChanged}" ></Label>
</StackPanel>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
<Label Style="{StaticResource TitleBarTime}">12:05 AM</Label>
<StackPanel Orientation="Horizontal">
<Label Style="{StaticResource TitleBarUsername}">Hassan</Label>
<Button>
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
</Button>
</StackPanel>
</StackPanel>
</DockPanel>
</Border>
</Grid>
<Frame Width="700" Height="507" Source="Pages/Dashboard.xaml" DataContext="{Binding CurrentViewModel}"/>
</StackPanel>
</Grid>
</Window>
And will then need to use some form of view location / navigation to change the frame to display the correct view. Some MVVM frameworks (for example, CaliburnMicro) can do this for you.
Again, in order to make this code testable, the instantiation of sub-viewmodels should be delegated to a factory class which is injected into the MainViewModel.
Hope it helps.
I don't know why but my Visibility Binding isn't working ONLY in the DataTemplate. Did I forget something?
Edit: All Bindings (except for this one work perfectly)
Thats the structure.
<Window>
<Grid>
<Grid>
<Grid Grid.Row="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ItemsControl x:Name="Targets" Margin="0,4,0,4">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,5,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox TextWrapping="Wrap" Foreground="{Binding Foreground, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Address, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" Tag="{Binding}" PreviewKeyDown="ChangeLocationAddress" PreviewGotKeyboardFocus="TxtGotKeyboardFocusHandler" LostKeyboardFocus="ChangeLocationAddress" />
<Button Margin="2,0,0,0" Grid.Column="1" Content=" ↑ " Click="MoveLocationUp" Visibility="Visible" />
<Button Margin="2,0,0,0" Grid.Column="2" Content=" ↓ " Click="MoveLocationDown" Visibility="{Binding Path = UpDownButtonVisibility, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button x:Name="btnNewAddress" Grid.Row="1" Content="Neues Ziel hinzufügen" Margin="0,4,0,4" Visibility="{Binding Path=TargetButtonVisibility}"/>
</Grid>
</Grid></Grid></Grid></Window>
Codebehind:
public MapView(){
this.DataContext = this.ViewModel = new MapVM();
this.InitializeComponent();
this.Targest.Itemssource = this.ViewModel.ToLocations;
}
ViewModel:
public MapVM()
{ this.UpDownButtonVisibility = Visibility.Collapsed;
this.TargetButtonVisibility = Visibility.Collapsed;
}
private Visibility _UpDownButtonVisibility;
/// <summary>
/// Property Visibility für "↓" und "↑"
/// </summary>
public Visibility UpDownButtonVisibility
{
get { return _UpDownButtonVisibility; }
set
{
this._UpDownButtonVisibility = value;
NotifyPropertyChanged("UpDownButtonVisibility");
}
}
public Visibility TargetButtonVisibility { get; set; }
EDIT:
Program Output:
BindingExpression path error: 'UpDownButtonVisibility' property not found on 'object' ''Location' (HashCode=-794088449)'. BindingExpression:Path=UpDownButtonVisibility; DataItem='Location' (HashCode=-794088449); target element is 'Button' (Name=''); target property is 'Visibility' (type 'Visibility') 1.10s
Any suggestions?
I cannot find a PropertyChanged event handler and a call to it in your code. Add the INotifyPropertyChanged to your DataContext object and it should work
Personally I would model the visibility as a bool and use the BooleasnToVisibility Converter that comes with WPF.
Change string to Visibility
public Visibility UpDownButtonVisibility { get; set; }
this.UpDownButtonVisibility = Visibility.Collapsed;
Add INPC and binding to a View model.
Here is working sample:
XAML
<Window x:Class="ItemsControlDataTemplate.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:local="clr-namespace:ItemsControlDataTemplate"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ItemsControl x:Name="Targets" Margin="0,4,0,4" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,5,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox TextWrapping="Wrap" Foreground="{Binding Foreground, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Address, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" Tag="{Binding}" />
<Button Margin="2,0,0,0" Grid.Column="1" Content=" ↑ " Visibility="Visible" />
<Button Margin="2,0,0,0" Grid.Column="2" Content=" ↓ " Visibility="{Binding Path=UpDownButtonVisibility}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
C#
class MainWindowViewModel : INotifyPropertyChanged
{
ObservableCollection<MapVM> _items = new ObservableCollection<MapVM>();
public ObservableCollection<MapVM> Items { get { return _items; } }
public MainWindowViewModel()
{
Items.Add(new MapVM() { UpDownButtonVisibility = Visibility.Visible, Address = "1111111" });
Items.Add(new MapVM() { UpDownButtonVisibility = Visibility.Collapsed, Address = "222222" });
Items.Add(new MapVM() { UpDownButtonVisibility = Visibility.Visible, Address = "33333333" });
}
public event PropertyChangedEventHandler PropertyChanged;
}
class MapVM : INotifyPropertyChanged
{
public MapVM()
{
this.UpDownButtonVisibility = Visibility.Collapsed;
this.TargetButtonVisibility = Visibility.Collapsed;
}
private Visibility _upDownButtonVisibility;
public Visibility UpDownButtonVisibility
{
get { return _upDownButtonVisibility; }
set
{
_upDownButtonVisibility = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UpDownButtonVisibility)));
}
}
private Visibility _targetButtonVisibility;
public Visibility TargetButtonVisibility
{
get { return _targetButtonVisibility; }
set
{
_targetButtonVisibility = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TargetButtonVisibility)));
}
}
private string _address;
public string Address
{
get { return _address; }
set
{
_address = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Address)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
The DataContext of your TargetButtonVisibility binding is your main MapVM. This works ok.
The DataContext within your DataTemplate is not MapVM, is it the item being displayed by the template.
Since you have not supplied any ItemsSource binding on your ItemsControl we have no way of knowing what this actually is.
Also, as unkreativ points out, do not use Visibility in your VM, since this is a view related type. Use bool instead and call the property IsUpDownButtonVisible or similar.
EDIT: Assuming you do actually want to bind to your single MapVM, you could use a RelativeSource to find the parent ItemsControl:
<Button Margin="2,0,0,0"
Grid.Column="2"
Content=" ↓ "
Click="MoveLocationDown"
Visibility="{Binding DataContext.UpDownButtonVisibility,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemsControl}}"/>
However, since you've already named your ItemsControl (Targets), you can simply refer to it by name:
<Button Margin="2,0,0,0"
Grid.Column="2"
Content=" ↓ "
Click="MoveLocationDown"
Visibility="{Binding DataContext.UpDownButtonVisibility,
ElementName=Targets}"/>
I currently have a working method, but does not work for what I need. Here's how the Settings window looks:
Right now I am binding the Department tab DataContext property to an ObservableCollection of Department objects, and then I just refer to the properties of the Department object in the text boxes to the right. Here is the code for this tab:
<TabItem Header="Department Settings"
DataContext="{Binding Departments}">
<DockPanel Margin="3,3,3,3">
<DockPanel DockPanel.Dock="Left"
Width="200">
<StackPanel DockPanel.Dock="Bottom"
Margin="0,5,0,0"
Orientation="Horizontal">
<TextBox x:Name="tbxAddDepartmentName"
Width="160"
Padding="0,5,0,5" />
<Button x:Name="btnAddDepartment"
Content="Add"
Margin="5,0,5,0"
Padding="5,5,5,5"
Command="{Binding AddDepartmentCommand, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<ListBox ItemsSource="{Binding}" />
</DockPanel>
<StackPanel DockPanel.Dock="Bottom">
<Button Content="Save Changes"
Padding="5,5,5,5"
HorizontalAlignment="Right" />
</StackPanel>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Labels -->
<Label Grid.Row="0" Grid.Column="0">Department Name:</Label>
<Label Grid.Row="1" Grid.Column="0">Report Number:</Label>
<Label Grid.Row="2" Grid.Column="0">Address:</Label>
<!-- Input -->
<TextBox x:Name="tbxDepartmentName"
Text="{Binding Name}"
Grid.Row="0" Grid.Column="1"
VerticalContentAlignment="Center"
Margin="0,5,0,5"
Padding="5" />
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding ReportNumber}"
VerticalContentAlignment="Center"
Margin="0,5,0,5"
Padding="5" />
<TextBox Grid.Row="2" Grid.Column="1"
Text="{Binding Address}"
VerticalContentAlignment="Center"
AcceptsReturn="True"
Margin="0,5,0,5"
Padding="5" />
</Grid>
</DockPanel>
</TabItem>
What I want to do is have a pointer to the specific Department object that is selected with the ListBox inside my ViewModel. This way, I should be able to do a one way binding on the textboxes to the right, and save change to the SQL Compact DB after I hit the "Save Changes" Button.
Sample ViewModel:
public class ViewModel : INotifyPropertyChanged, INotifyPropertyChanging
{
public class MyObj
{
public string Test1 { get; set; }
public string Test2 { get; set; }
}
#region selectedItem
private MyObj _selectedItem;
public MyObj selectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem != value)
{
NotifyPropertyChanging("selectedItem");
_selectedItem = value;
NotifyPropertyChanged("selectedItem");
}
}
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify the page that a data context property changed
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
// Used to notify the data context that a data context property is about to change
protected void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
Add to your ListBox:
SelectedItem="{Binding selectedItem, UpdateSourceTrigger=PropertyChanged}"
Your dataContext should be whole ViewModel, not a collection.
Then you can set in listBox
ItemsSource="{Binding Departments}"
and in the Grid
DataContext="{Binding Departments}"
There in my mainwindow.xaml I've got the grid with:
<CustomControl:GridControl ShowCustomGridLines="True" Grid.Column="2" Grid.Row="0">
<local:_13cap/>
</CustomControl:GridControl>
Where 13cap is my custom control:
<UserControl x:Class="TTTP._13cap"
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:CustomControl="clr-namespace:WpfApplication1.CustomControl"
mc:Ignorable="d">
<CustomControl:GridControl ShowCustomGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Text="ВСЕГО" />
<CustomControl:GridControl ShowCustomGridLines="True" Grid.Column="2" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="1" Text="П" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock Grid.Column="1" Grid.Row="1" Text="Ф" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock Grid.Column="2" Grid.Row="1" Text="%" HorizontalAlignment="Center" VerticalAlignment="Center" />
</CustomControl:GridControl>
</CustomControl:GridControl>
</UserControl>
But I want to call it with only one different text parameter ( Text="ВСЕГО" ) alike I want to call <local:_13cap MyText="CustomText"/> to override "ВСЕГО". How can I create UserControl with such parameter in it?
Implement a DependancyProperty that you can change from code-behind and bind to in XAML.
Code behind:
public class MyUserControl : UserControl, INotifyPropertyChanged
{
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
//The XAML binding uses this
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(MyUserControl),
new PropertyMetadata(string.Empty, OnCaptionPropertyChanged));
//Your code-behind uses this
public string Caption
{
get { return GetValue(CaptionProperty).ToString(); }
set
{
SetValue(CaptionProperty, value);
OnPropertyChanged("Caption");
}
}
Xaml:
<UserControl (...)>
<Grid>
<Label x:Name="labCaption" Content="{Binding Caption}"/>
</Grid>
</UserControl>
There are lots of other questions here about this and lots of good articles and tutorial on google that explain it all better then I could.