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.
Related
I am trying to create a "notes" app using wpf mvvm. I have a MainWindow containing a DataGrid with data that is bound to an ObservableCollection. In MainWindowView I have a "Find" button which calls FindWindowDialog. In the FindWindowDialog in the textbox, I must enter the text that will be searched and click "Find", after which the DataGrid MainWindowView should hide those lines whose content does not contain the searched text. I don't really know how to do this, after 2 days of searching I decided to ask a question. I googled on this topic and I have a suggestion that I should delve into the messenger pattern and converters
MainWindow.xaml(View)
<Window x:Class="NotesARK6.View.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:NotesARK6.View"
xmlns:model="clr-namespace:NotesARK6.Model"
xmlns:viewmodel="clr-namespace:NotesARK6.ViewModel"
mc:Ignorable="d"
Title="Notes" Height="450" Width="800"
x:Name="_mainWindow"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding NotesCollection}" SelectedItem="{Binding SelectedNote}" IsReadOnly="True" AutoGenerateColumns="False" x:Name="DataGrid_Notes" Margin="5" Grid.Row="2" Grid.Column="1">
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding EditNoteCommand}" CommandParameter="{Binding SelectedNote}" />
</DataGrid.InputBindings>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="1*" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Content" Width="3*" Binding="{Binding Content}"/>
</DataGrid.Columns>
</DataGrid>
<ToolBar Grid.Row="1" Grid.Column="1" Margin="5">
<Button Content="Create" Command="{Binding CreateNewNoteCommand}"/>
<Separator />
<Button Content="Delete" Command="{Binding DeleteNoteCommand}" CommandParameter="{Binding SelectedNote}"/>
<Separator />
<Button Content="Find" Command="{Binding FindNoteCommand}"/>
</ToolBar>
</Grid>
</Window>
FindWindowDialog.xaml(view)
<Window x:Class="NotesARK6.ViewModel.Dialogs.FindWindowDialog"
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:NotesARK6.ViewModel.Dialogs"
mc:Ignorable="d"
Title="Find" Height="250" Width="400"
WindowStartupLocation="CenterScreen"
Topmost="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="35"/>
<RowDefinition Height="60"/>
<RowDefinition Height="50"/>
<RowDefinition Height="90"/>
</Grid.RowDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding SearchByName}" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="5" Content="Search by name" Grid.Column="0"></CheckBox>
<CheckBox IsChecked="{Binding SearchByContent}" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="5" Content="Search by content" Grid.Column="1"></CheckBox>
</Grid>
<TextBox Text="{Binding SearchString}" Margin="5" Grid.Row="2" Grid.Column="1"/>
<Button Command="{Binding FindNotesCommand}" Margin="5" Content="Find" Grid.Row="3" Grid.Column="1" />
</Grid>
</Window>
FindWindowDialogViewModel.cs
public class FindWindowDialogViewModel : INotifyPropertyChanged
{
private string searchString;
private bool searchByName;
private bool searchByContent;
//Controll Commands
public ControllComands FindNotesCommand { get; private set; }
//Controll Commands
public FindWindowDialogViewModel()
{
FindNotesCommand = new ControllComands(FindNote);
}
public string SearchString
{
get
{
return searchString;
}
set
{
searchString = value;
OnPropertyChanged();
}
}
public bool SearchByName
{
get
{
return searchByName;
}
set
{
searchByName = value;
OnPropertyChanged("SearchByName");
}
}
public bool SearchByContent
{
get
{
return searchByContent;
}
set
{
searchByContent = value;
OnPropertyChanged("SearchByContent");
}
}
public void FindNote()
{
MessageBox.Show(SearchByName.ToString() + " " + SearchByContent.ToString());
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string prop = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
How can I use the command contained in FindWindowDialogViewModel to hide rows in the DataGrid MainWindowView ?
I would like something like this: (this is pseudocode)
public void FindNote()
{
foreach(var row in MainWindow.DataGrid.Rows)
{
string searchingText = FindNoteDialog.TextBox.Text;
if (!row.Content.Contains(searchingText))
{
row.Visibillity = false;
}
}
}
For this purpose collections provide filtering via their views (see Binding to collections and CollectionView API remarks to learn more).
Filtering using the collection's view has much better performance than hiding rows or removing items from the original collection.
To do so, you must get the ICollectionView of the source collection
Note.cs
class Note
{
public string Summary { get; set; }
public DateTime Timestamp { get; set; }
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<Note> Notes { get; }
public string SearchKey { get; set; }
public ICommand FilterNotesCommand => new RelayCommand(ExecuteFilterNotes);
private void ExecuteFilterCommands(object commandParameter)
{
ICollectionView notesView = CollectionViewSource.GetDefaultView(this.Notes);
notesView.Filter = item => (item as Note).Summary.Contains(this.SearchKey);
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding SearchKey}" />
<Button Command="{Binding FilterNotesCommand}" Content="Filter Table" />
<DataGrid ItemsSource="{Binding Notes}" />
</StackPanel>
</Window>
I have a ContentControl in DayView.xaml whose content binds to the CurrentSongViewModel property in DayViewModel.cs . The CurrentSongViewModel is DataTemplated in the ContentControl to display its respective view depending on the view model (either DefaultSongViewModel or EditSongViewModel), and both of the DataTemplates are confirmed to work. When I click the 'Edit' button on DefaultSongView.xaml, the EditSongCommand executes and sets the CurrentSongViewModel to a new EditSongViewModel. The CurrentSongViewModel setter calls OnPropertyChanged(), but the ContentControl content is not updating! I have set break points on the OnPropertyChanged() call and it is calling it. I have no idea why its not updating...
DayView.xaml
<UserControl x:Class="Calandar.Views.DayView"
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"
d:DataContext="{d:DesignInstance Type=viewmodels:DayViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightSteelBlue">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Day" Grid.Row="0" FontSize="35" FontFamily="Yu Gothic UI Semibold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/>
<ColumnDefinition Width="400"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" >
<Border Style="{StaticResource PurpleBorder}">
<!-- The binding that isnt working -->
<ContentControl Content="{Binding CurrentSongViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewmodels:DefaultSongViewModel}">
<local:DefaultSongView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:EditSongViewModel}">
<local:EditSongView/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Border>
</StackPanel>
</Grid>
</Grid>
</UserControl>
DefaultSongView.xaml
<UserControl x:Class="Calandar.Views.DefaultSongView"
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"
d:DataContext="{d:DesignInstance Type=viewmodels:DayViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.DataContext>
<viewmodels:DayViewModel/>
</Grid.DataContext>
<StackPanel>
<DockPanel >
<Button Content="Edit" Command="{Binding EditSongCommand}"
Style="{StaticResource CollectionModifierButton}" DockPanel.Dock="Right"/>
<TextBlock Text="Songs" Style="{StaticResource BoxTitleText}" DockPanel.Dock="Top"/>
</DockPanel>
<ListBox ItemsSource="{Binding SongList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=.}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</UserControl>
DayViewModel.cs
public class DayViewModel : ViewModelBase
{
// content view models
private ViewModelBase _currentSongViewModel;
public ViewModelBase CurrentSongViewModel
{
get { return _currentSongViewModel; }
set
{
_currentSongViewModel = value;
OnPropertyChanged(nameof(CurrentSongViewModel));
}
}
// Song list
public ObservableCollection<string> SongList { get; set; }
// Commands
public ICommand EditSongCommand => new EditSongsCommand(this);
// Constructor
public DayViewModel()
{
_currentSongViewModel = new DefaultSongViewModel();
SongList = new ObservableCollection<string>();
foreach (string line in File.ReadLines(#"C:\Users\person\source\repos\Calandar\DataFiles\Songs.txt"))
{
SongList.Add(line);
}
}
}
EditSongCommand.cs
public class EditSongsCommand : CommandBase
{
DayViewModel _vm;
public override bool CanExecute(object parameter) => true;
public override void Execute(object parameter)
{
_vm.CurrentSongViewModel = new EditSongViewModel();
}
public EditSongsCommand(DayViewModel vm)
{
_vm = vm;
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string PropertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
}
The following markup sets the DataContext to another instance of the DayViewModel:
<Grid.DataContext>
<viewmodels:DayViewModel/>
</Grid.DataContext>
You should remove it from DefaultSongView.xaml and bind to the command of the parent view model using a RelativeSource:
<Button
Content="Edit"
Command="{Binding DataContext.EditSongCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}"
I am adding a record from view 2 in the database;
1. The record is inserted successfully and is instantly shown in the usercontrol(DataGrid) on the same view(View2)
2. The changes are not shown in view 1 Unless I Close the the view or application and start it again.
// The INotifyProperty is implemented in ViewModelBase Class
I used the same view model on both views.
but I think it creates a new instance of the view model and datacontext on each view.
I want to resolve this issue.
---------------------------View 1-------------------------------------
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:UC="clr-namespace:HRSimplified.Controls"
x:Class="HRSimplified.MainWindow"
Title="MainWindow"
Height="628" Width="986">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<UC:EmployeeGridControl Grid.Column="1" />
</Grid>
</Window>
---------------------------View 2--------------------------------------
<Window
x:Class="HRSimplified.Windows.EmployeeDashboard"
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:local="clr-namespace:HRSimplified.View"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:VM="clr-namespace:HRSimplified.ViewModel"
xmlns:UC="clr-namespace:HRSimplified.Controls"
mc:Ignorable="d"
Title="EmployeeDashboard">
<Window.DataContext>
<VM:ViewModel_Employee />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<UC:EmployeeGridControl />
<ItemsControl Grid.Column="1" ItemsSource="{Binding Path=emp}">
<StackPanel>
<TextBox EditValue="{Binding emp.Name, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True}" Margin="5" />
<Button Command="{Binding AddCommand, UpdateSourceTrigger=PropertyChanged, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, ValidatesOnDataErrors=True}" Margin="5" Height="35" />
</StackPanel>
</ItemsControl>
</Window>
-------------------------UserControl----------------------------------
<UserControl
x:Class="HRSimplified.Controls.EmployeeGridControl"
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:HRSimplified.View"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:VM="clr-namespace:HRSimplified.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid>
<DataGrid x:Name="MasterData" MaxHeight="1080" ItemsSource="{Binding MasterData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True}">
<DataGridTextColumn x:Name="Name" Binding="{Binding Mode=TwoWay}"
/>
</DataGrid>
</Grid>
</UserControl>
--------------------ViewModel_Employe---------------------------------
public class ViewModel_Employee:ViewModelBase{
public HRSimplifiedEntities Model = new HRSimplifiedEntities();
private ObservableCollection<Model.Employee> _MasterData;
public ObservableCollection<Model.Employee> MasterData
{
get
{
return _MasterData;
}
set
{
SetProperty(ref this._MasterData, value);
}
}
private Employee _emp;
private ICommand _submitCommand;
public Employee emp
{
get { return _emp; }
set
{
_emp = value;
OnPropertyChanged("EmployeeCollection");
}
}
public ViewModel_Employee()
{
MasterData = new ObservableCollection<Model.Employee>
(Model.Employees.ToList() as IEnumerable<Employee>);
}
public ICommand AddCommand
{
get
{
if (_submitCommand== null)
{
_submitCommand = new RelayCommand(executeMethod, canExecuteMethod, false);
}
return _submitCommand;
}
}
private bool canExecuteMethod(object parameter)
{
if (string.IsNullOrEmpty(emp.Name) || string.IsNullOrEmpty(emp.Gender) ||
string.IsNullOrEmpty(emp.Salary.ToString()))
return false;
else
return true;
}
private void executeMethod(object parameter)
{
MasterData.Add(emp);
Model.Employees.Add(emp);
Model.SaveChanges();
System.Media.SystemSounds.Beep.Play();
}}
I think you can create a Singleton object of ViewModel_Employee and use as DataContext in all your views.
SingletonClass
public class ViewModel_Employee : INotifyPropertyChanged
{
private ViewModel_Employee()
{
}
private static ViewModel_Employee privateInstance = null;
public static ViewModel_Employee Instance
{
get
{
if (privateInstance == null)
privateInstance = new ViewModel_Employee();
return privateInstance;
}
// class is a singleton
}
DataContext in all Views:
<Window.DataContext >
<x:StaticExtension Member="VM:ViewModel_Employee.Instance" />
</Window.DataContext>
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}"/>