WPF binding works only once - c#

I'm developing a C# project in which I've to use WPF and I'm encountering some problems with binding dynamic content to UI.
Briefly, I have this textBlock here declared in a file called NotifyIconResources.xaml:
<TextBlock Name="label_stato" Text="{Binding Source={x:Static Application.Current}, Path=Properties[status]}"/>
It shows the value of a string property, called status, declared in App.xaml.cs:
public partial class App : Application, INotifyPropertyChanged
{
private MainWindow mw;
private TaskbarIcon notifyIcon;
public event PropertyChangedEventHandler PropertyChanged;
private string _status;
private string status
{
get
{
return _status;
}
set
{
_status = value;
NotifyPropertyChanged("status");
}
}
/// <summary>
/// Triggers a GUI update on a property change
/// </summary>
/// <param name="propertyName"></param>
private void NotifyPropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
this.Properties["status"] = "Online";
//create the notifyicon (it's a resource declared in NotifyIconResources.xaml
ResourceDictionary rd = new ResourceDictionary
{
Source = new Uri("/NotifyIconResources.xaml", UriKind.RelativeOrAbsolute)
};
notifyIcon = (TaskbarIcon)rd["NotifyIcon"];
mw = new MainWindow(Environment.GetCommandLineArgs()[1]);
mw.Show();
}
}
The value of the property is dynamic, its initial value is showed in the textBlock, but the next modifications of its value don't result in a refresh of the textBlock content.
NotifyIconResources.xaml is declared in App.xaml:
<Application x:Class="FileSharingOnLAN.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FileSharingOnLAN"
StartupUri="MainWindow.xaml"
ShutdownMode="OnExplicitShutdown">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="NotifyIconResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
<Application.MainWindow>
<NavigationWindow Source="MainWindow.xaml" Visibility="Visible"></NavigationWindow>
</Application.MainWindow>
The NotifyIconResources.xaml file:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FileSharingOnLAN"
xmlns:tb="http://www.hardcodet.net/taskbar">
<tb:TaskbarIcon x:Key="NotifyIcon"
IconSource="/Red.ico"
ToolTipText="Click for popup, right-click for menu"
ContextMenu="{StaticResource SysTrayMenu}">
<tb:TaskbarIcon.TrayPopup>
<Border
Background="Black"
Width="200"
Height="100">
<StackPanel>
<Button
Content="{Binding ButtonContent}"
VerticalAlignment="Top"
Command="{Binding ShowImpostazioniWindowCommand}"/>
<Border
Background="Blue"
Width="100"
Height="50"
VerticalAlignment="Bottom"
HorizontalAlignment="Left">
<StackPanel>
<Label
Content="Stato" />
<!--<Button
Content="{Binding Path="status", Source="{x:Static Application.Current}"}"
Command="{Binding StatusClickedCommand}"/>-->
<TextBlock Name="label_stato" Text="{Binding Source={x:Static Application.Current}, Path=Properties[status]}"/>
</StackPanel>
</Border>
</StackPanel>
</Border>
</tb:TaskbarIcon.TrayPopup>
<tb:TaskbarIcon.DataContext>
<local:NotifyIconViewModel />
</tb:TaskbarIcon.DataContext>
</tb:TaskbarIcon>
I've tried to fix it by using OnpropertyChanged, like in this: Binding to change the property but it didn't work.
In the same project I use other binding techniques which work and I know how to solve this problem programmatically but I want to understand where my error is, I'm totally stuck with it.

You have few things that needs to be changed:
First one is status property it self, make it public:
public string Status
{
get
{
return (string)this.Properties["status"];
}
set
{
this.Properties["status"] = value;
NotifyPropertyChanged("Status");
}
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Status = "Online";
...
}
And change your binding to:
<TextBlock Text="{Binding Source={x:Static Application.Current}, Path=Status}" />
Working Example:
<Window x:Class="WpfApplicationTest.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:WpfApplicationTest"
mc:Ignorable="d"
x:Name="win"
Title="MainWindow" Height="300" Width="300">
<Window.Resources>
</Window.Resources>
<StackPanel FocusManager.FocusedElement="{Binding ElementName=btnDefault}">
<TextBox Text="{Binding Source={x:Static Application.Current}, Path=Status}" />
<TextBlock Text="{Binding Source={x:Static Application.Current}, Path=Status}" />
<Button>Button 7</Button>
</StackPanel>
</Window>

Found a working solution.
I was changing the value of the property in another cs module like this:
Application.Current.Properties["status"] = "Offline";
This line cannot result in a modification of the property, so I wasn't modifying it. So, thanks to https://stackoverflow.com/a/1609155/10173298, I solved it by substituting that line with:
((App)Application.Current).setOffline();
Where setOffline() is a method I've added to App.xaml.cs:
public void setOffline()
{
this.Status = "Offline";
}

Related

Closing an Open Window Using MVVM Pattern, Produces System.NullReferenceException error

I'm trying to learn MVVM pattern using WPF C#. And I'm running into an error when trying to close an opened window after saving information to an sqlite database. When the command to save a new contact is raised, I am getting an error on HasAddedContact(this, new EventArgs());
Error: System.NullReferenceException: 'Object reference not set to an instance of an object.'
My ViewModel:
public class NewContactViewModel : BaseViewModel
{
private ContactViewModel _contact;
public ContactViewModel Contact
{
get { return _contact; }
set { SetValue(ref _contact, value); }
}
public SaveNewContactCommand SaveNewContactCommand { get; set; }
public event EventHandler HasAddedContact;
public NewContactViewModel()
{
SaveNewContactCommand = new SaveNewContactCommand(this);
_contact = new ContactViewModel();
}
public void SaveNewContact()
{
var newContact = new Contact()
{
Name = Contact.Name,
Email = Contact.Email,
Phone = Contact.Phone
};
DatabaseConnection.Insert(newContact);
HasAddedContact(this, new EventArgs());
}
}
SaveNewContactCommand:
public class SaveNewContactCommand : ICommand
{
public NewContactViewModel VM { get; set; }
public SaveNewContactCommand(NewContactViewModel vm)
{
VM = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
VM.SaveNewContact();
}
}
NewContactWindow.Xaml.Cs code behind:
public partial class NewContactWindow : Window
{
NewContactViewModel _viewModel;
public NewContactWindow()
{
InitializeComponent();
_viewModel = new NewContactViewModel();
DataContext = _viewModel;
_viewModel.HasAddedContact += Vm_ContactAdded;
}
private void Vm_ContactAdded(object sender, EventArgs e)
{
this.Close();
}
}
Adding additional code where I call the new window:
public class ContactsViewModel
{
public ObservableCollection<IContact> Contacts { get; set; } = new ObservableCollection<IContact>();
public NewContactCommand NewContactCommand { get; set; }
public ContactsViewModel()
{
NewContactCommand = new NewContactCommand(this);
GetContacts();
}
public void GetContacts()
{
using(var conn = new SQLite.SQLiteConnection(DatabaseConnection.dbFile))
{
conn.CreateTable<Contact>();
var contacts = conn.Table<Contact>().ToList();
Contacts.Clear();
foreach (var contact in contacts)
{
Contacts.Add(contact);
}
}
}
public void CreateNewContact()
{
var newContactWindow = new NewContactWindow();
newContactWindow.ShowDialog();
GetContacts();
}
}
ContactsWindow.Xaml
<Window x:Class="Contacts_App.View.ContactsWindow"
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:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="Contacts Window" Height="320" Width="400">
<Window.Resources>
<vm:ContactsViewModel x:Key="vm"/>
</Window.Resources>
<StackPanel Margin="10">
<Button
Content="New Contact"
Command="{Binding NewContactCommand}"/>
<TextBox Margin="0,5,0,5"/>
<ListView
Height="200"
Margin="0,5,0,0"
ItemsSource="{Binding Contacts}">
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>
NewContactWindow.Xaml
<Window x:Class="Contacts_App.View.NewContactWindow"
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:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="New Contact Window" Height="250" Width="350">
<Window.Resources>
<vm:NewContactViewModel x:Key="vm"/>
</Window.Resources>
<Grid>
<StackPanel
Margin="10">
<Label Content="Name" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Email" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Phone Number" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Phone, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Button
Content="Save"
Command="{Binding Source={StaticResource vm}, Path=SaveNewContactCommand}"/>
</StackPanel>
</Grid>
</Window>
You're creating NewContactWindow's viewmodel in the constructor, correctly assigning it to DataContext, and correctly adding a handler to that event. Unfortunately, you also create a second instance of the same viewmodel in resources, and you manually set the Source property of all the bindings to use the one in the resources, which doesn't have the event handler.
Window.DataContext, which you set in the constructor, is the default Source for any binding in the Window XAML. Just let it do its thing. I also removed all the redundant Mode=TwoWay things from the Bindings to TextBox.Text, since that property is defined so that all bindings on it will be TwoWay by default. I don't think UpdateSourceTrigger=PropertyChanged is doing anything necessary or helpful either: That causes the Binding to update your viewmodel property every time a key is pressed, instead of just when the TextBox loses focus. But I don't think you're doing anything with the properties where that would matter; there's no validation or anything. But TextBox.Text is one of the very few places where that's actually used, so I left it in.
You should remove the analagous viewmodel resource in your other window. It's not doing any harm, but it's useless at best. At worst, it's an attractive nuisance. Kill it with fire and bury the ashes under a lonely crossroads at midnight.
<Window x:Class="Contacts_App.View.NewContactWindow"
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:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="New Contact Window" Height="250" Width="350">
<Grid>
<StackPanel
Margin="10">
<Label Content="Name" />
<TextBox
Text="{Binding Contact.Name, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Email" />
<TextBox
Text="{Binding Contact.Email, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Phone Number" />
<TextBox
Text="{Binding Contact.Phone, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Button
Content="Save"
Command="{Binding SaveNewContactCommand}"/>
</StackPanel>
</Grid>
</Window>

Why the first launch of my WPF application shows the class name of a ViewModel instead of its content (properties)?

I bind a MainWindowViewModel to the DataContext of a MainWindow.
Then I initialize this MainWindowViewModel to a specific itemsPageViewModel.
The problem is that on startUp I see itemsPageViewModel 's class name instead of its content:
Startup
However, after switching pages through buttons (RelayCommands), the same ViewModel now shows its content:
PageSwitched
Both operations pass through the same code-line:
CurrentPageViewModel = _itemsPageViewModel
How can it produce different results?
CODE
MainWindow.xaml
<Window x:Class="ListItemUI.Views.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"
mc:Ignorable="d"
Title="ListItemUI" Height="400" Width="600">
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<StackPanel Orientation="Horizontal">
<Button Content="ITEMS" Margin="2" Command ="{Binding SelectItemsPageViewModel}"></Button>
<Button Content="HELP" Margin="2" Command ="{Binding SelectInfoPageViewModel}"></Button>
</StackPanel>
</Grid>
<ContentControl Grid.Row="2" Content="{Binding CurrentPageViewModel}"/>
</Grid>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using ListItemUI.InfoPage.ViewModels;
using ListItemUI.ListItemPage.ViewModels;
using ListItemUI.ViewModels;
namespace ListItemUI.Views
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow(IPageFactory itemPageFactory, IPageFactory infoPageFactory)
{
InitializeComponent();
var mainWindowVM = new MainWindowViewModel(itemPageFactory,infoPageFactory);
DataContext = mainWindowVM;
}
}
}
MainWindowViewModel.cs
using System;
using System.Windows.Input;
using ListItemUI.ListItemPage.ViewModels;
namespace ListItemUI.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private readonly IListItemUIViewModel _itemsPageViewModel;
private readonly IListItemUIViewModel _infoPageViewModel;
public ICommand SelectItemsPageViewModel { get; }
public ICommand SelectInfoPageViewModel { get; }
public object CurrentPageViewModel
{
get { return _currentPageViewModel; }
set
{
_currentPageViewModel = value;
RaisePropertyChanged(() => CurrentPageViewModel);
}
}
private object _currentPageViewModel;
public MainWindowViewModel(IPageFactory itemsPageFactory, IPageFactory infoPageFactory)
{
_itemsPageViewModel = itemsPageFactory.CreatePage();
_infoPageViewModel = infoPageFactory.CreatePage();
SelectItemsPageViewModel = new RelayCommand(_ =>
{
CurrentPageViewModel = _itemsPageViewModel;
});
SelectInfoPageViewModel = new RelayCommand(_ =>
{
CurrentPageViewModel = _infoPageViewModel;
});
CurrentPageViewModel = _itemsPageViewModel;
}
}
}
ListItemPage.xaml (dataTemplates)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels ="clr-namespace:ListItemUI.ListItemPage.ViewModels">
<DataTemplate DataType="{x:Type viewModels:ItemViewModel}">
<StackPanel>
<TextBlock Foreground="RoyalBlue" FontWeight="Bold" Text="{Binding Path=ItemViewDescription, StringFormat='Group Info = {0}'}"></TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:ItemsPageViewModel}">
<StackPanel>
<TextBlock Text ="{Binding Path=Title}"></TextBlock>
<Grid Grid.Column="0" Background="Aquamarine">
<ListBox ItemsSource="{Binding Path=LocalItemViewModels}" Margin="5">
</ListBox>
</Grid>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
App.xaml
<Application x:Class="ListItemUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ListItemPage/Views/ListItemPage.xaml"></ResourceDictionary>
<ResourceDictionary Source="InfoPage/Views/InfoView.xaml"></ResourceDictionary>
<!--GLOBAL RESOURCES -->
<ResourceDictionary Source="Views/GlobalResources.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
We used a workaround to solve this specific problem.
We chose not to use resource dictionaries, putting the dataTemplates of the viewModels directly into the mainwindow.xaml: now everything works.
Something strange happens when we use resource dictionaries.

MVVM Listbox in tab control is not updating

I'm struggling with update issue. I have tab control with the listbox binded to an Observable Collection
ListBox HorizontalAlignment="Center" Height="450" VerticalAlignment="Top" Width="250"
x:Name="LbxMenu" Background="{x:Null}" BorderBrush="{x:Null}"
ItemsSource="{Binding TestListsNames}" FontFamily="Segoe UI Semilight" FontSize="18"/>
view model:
private ObservableCollection<string> _testListsName;
public ObservableCollection<string> TestListsNames
{
get { return _testListsName; }
set{ _testListsName = value; }
}
After inserting entity to database there is an event which invokes TestListInitialize method in my ViewModel which should refresh collection and it works as I can see it in debugger. But listbox doesn't refresh and I have to restart application to see changes.
It worked great when it was in separate window but when I changed ui to tab control it doesn't.
Update function:
private void TestListNamesInitialize()
{
TestListsNames = db.GetTestListNamesFromDatabase();
if (TestListsNames.Count != 0) CanLoad = true;
}
Initial Window:
<Controls:MetroWindow
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Test.View.InitialWindow"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:tabdata="clr-namespace:Test.View.TabItems"
Title="Testownik" Height="600" Width="900" ShowTitleBar="True" ResizeMode="NoResize" Icon="../GraphicResources/Icon.ico">
<Controls:MetroWindow.RightWindowCommands>
<Controls:WindowCommands>
<Button Content="settings" />
<Button>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="4 0 0 0"
VerticalAlignment="Center"
Text="about" />
</StackPanel>
</Button>
</Controls:WindowCommands>
</Controls:MetroWindow.RightWindowCommands>
<Controls:MetroAnimatedTabControl x:Name ="MainTabControl">
<TabItem Header="Learn" Width="280">
<tabdata:LearnTabItem/>
</TabItem>
<TabItem Header="Database" Width="280">
<tabdata:DatabaseTabItem/>
</TabItem>
<TabItem Header="Statistics" Width="299">
<tabdata:StatisticsTabItem/>
</TabItem>
</Controls:MetroAnimatedTabControl>
Code behind:
public partial class InitialWindow : MetroWindow
{
InitialWindowViewModel viewModel=new InitialWindowViewModel();
public InitialWindow()
{
InitializeComponent();
DataContext = viewModel;
}
}
}
DatabaseTabItem:
<UserControl x:Class="Test.View.TabItems.DatabaseTabItem"
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:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:tabData="clr-namespace:Test.View.TabItems"
Height="500" Width="900" Background="White" BorderBrush="Transparent">
<UserControl.Resources>
</UserControl.Resources>
<Grid>
<Controls:MetroAnimatedTabControl x:Name ="DatabaseTabControl" Grid.Column="0" TabStripPlacement="Left" >
<TabItem Header="Choose" Width="250" >
<tabData:ChooseFromDbTabItem/>
</TabItem>
<TabItem Header="Add" Width="250">
<tabData:AddToDbTabItem/>
</TabItem>
<TabItem Header="Remove" Width="250">
<tabData:DeleteFromDbTabItem/>
</TabItem>
</Controls:MetroAnimatedTabControl>
</Grid>
code behind:
DatabaseViewModel vm = new DatabaseViewModel();
public DatabaseTabItem()
{
InitializeComponent();
DataContext = vm;
}
}
ChooseFromDbTabItem:
<UserControl x:Class="Test.View.TabItems.ChooseFromDbTabItem"
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:Test.View.TabItems"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="650" Background="White" BorderBrush="Transparent">
<Grid>
<ListBox HorizontalAlignment="Center" Height="450" VerticalAlignment="Top" Width="250"
x:Name="LbxMenu" Background="{x:Null}" BorderBrush="{x:Null}"
ItemsSource="{Binding TestListsNames}" FontFamily="Segoe UI Semilight" FontSize="18"/>
</Grid>
code behind:
public partial class ChooseFromDbTabItem : UserControl
{
public ChooseFromDbTabItem()
{
InitializeComponent();
}
}
You have to rise PropertyChanged event for the reason you change your entire collection and not a single item (if you changed a single item that was updated through the Observable).
private ObservableCollection<string> _testListsName;
public ObservableCollection<string> TestListsNames
{
get { return _testListsName; }
set
{
if (_testListsName != value)
{
_testListsName = value;
NotifyPropertyChanged("TestListsNames");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
You are not raising the PropertyChanged event when replacing the list using the property setter. Generelly, try to make collection properties readonly to reduce the risk for these sort of errors. Instead, clear the list and repopulate it. This will make sure that the view is notified about any changes.
public class ViewModel
{
private readonly ObservableCollection<string> _testListsName;
public ObservableCollection<string> TestListsNames
{
get { return _testListsName; }
}
private void TestListNamesInitialize()
{
_testListsName.Clear();
foreach(string name in db.GetTestListNamesFromDatabase())
{
_testListsName.Add(name);
}
if (_testListsNames.Count != 0) CanLoad = true;
}
}
However, note that this will raise changed events on each item using the .Add() call. See here: Can I somehow temporarily disable WPF data binding changes?
Edit: from your updated code. It can also be seen that you do not set the DataContext on your ChooseFromDbTabItem. You need to bind the DataContext property to the view model that exposes the collection:
<TabItem Header="Choose" Width="250" >
<tabData:ChooseFromDbTabItem DataContext="{Binding}" />
</TabItem>

Binding DataTemplate Control Property

I have a UserControl like the following:
<UserControl>
<Expander>
<Expander.HeaderTemplate>
<DataTemplate>
<Grid HorizontalAlignment="{Binding Path=HorizontalAlignment, RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}, Mode=OneWayToSource}">
<TextBlock Text="{Binding Path=Service, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</Expander.HeaderTemplate>
</Expander>
</UserControl>
I want to bind the Text property of the TextBlock control to a property of my UserControl class, like for example:
public string Service
{ get; set; }
How can I do?
Try setting the DataContext to the UserControl so you can access the properties
In this situation I have named the UserControl "UI" (Name="UI") so you can bind using ElementName Text="{Binding ElementName=UI, Path=Service}"
Example:
<UserControl x:Class="WpfApplication8.UserControl1"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Name="UI">
<Expander>
<Expander.HeaderTemplate>
<DataTemplate>
<Grid HorizontalAlignment="{Binding Path=HorizontalAlignment, RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}, Mode=OneWayToSource}">
<TextBlock Text="{Binding ElementName=UI, Path=Service}" />
</Grid>
</DataTemplate>
</Expander.HeaderTemplate>
</Expander>
</UserControl>
Code:
I have implemented INotifyPropertyChanged this will allow the UI to be updated when Service string changes
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public UserControl1()
{
InitializeComponent();
Service = "Test";
}
private string _service;
public string Service
{
get { return _service; }
set { _service = value; NotifyPropertyChanged("Service"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Result:

WPF data templates

I'm getting started with WPF and trying to get my head around connecting data to the UI. I've managed to connect to a class without any issues, but what I really want to do is connect to a property of the main window.
Here's the XAML:
<Window x:Class="test3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:test3"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=Platforms}"
x:Key="platforms"/>
<DataTemplate DataType="{x:Type custom:Platform}">
<StackPanel>
<CheckBox IsChecked="{Binding Path=Selected}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource platforms}}"/>
</Grid>
Here's the code for the main window:
public partial class MainWindow : Window
{
ObservableCollection<Platform> m_platforms;
public MainWindow()
{
m_platforms = new ObservableCollection<Platform>();
m_platforms.Add(new Platform("PC"));
InitializeComponent();
}
public ObservableCollection<Platform> Platforms
{
get { return m_platforms; }
set { m_platforms = value; }
}
}
Here's the Platform class:
public class Platform
{
private string m_name;
private bool m_selected;
public Platform(string name)
{
m_name = name;
m_selected = false;
}
public string Name
{
get { return m_name; }
set { m_name = value; }
}
public bool Selected
{
get { return m_selected; }
set { m_selected = value; }
}
}
This all compiles and runs fine but the list box displays with nothing in it. If I put a breakpoint on the get method of Platforms, it doesn't get called. I don't understand as Platforms is what the XAML should be connecting to!
Your code looks ok apart from the fact that the Binding on Source on CollectionViewSource is not correct. You probably meant this:
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=MainWindow.Platforms}"
x:Key="platforms"/>
Without this change the Binding actually looked for property Platforms on Application instance.
I would suggest you add the platforms not to the MainWindow but rather set it as the MainWindow's DataContext (wrapped inside a ViewModel).
That way you can very easily bind against it (the binding code would look like ItemsSource={Binding Path=Platforms}).
This is part of WPFs design, that every form should have a explicit DataContext it binds to.
A somewhat more appropriate solution is to give your window a name. A nice convention is _this.
<Window x:Name="_this" x:Class="test3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:test3"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource
Source="{Binding ElementName=_this, Path=Platforms}"
x:Key="platforms"/>
<DataTemplate DataType="{x:Type custom:Platform}">
<StackPanel>
<CheckBox IsChecked="{Binding Path=Selected}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource platforms}}"/>
</Grid>
Here's my updated code, the XAML:
<Window x:Name="_this"
x:Class="test3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:test3"
Title="MainWindow" Height="190" Width="177">
<Window.Resources>
<CollectionViewSource
Source="{Binding ElementName=_this, Path=Platforms}"
x:Key="platforms"/>
<DataTemplate x:Key="platformTemplate" DataType="{x:Type custom:Platform}">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="1" IsChecked="{Binding Path=Selected}"/>
<TextBlock Margin="1" Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="23" />
<RowDefinition Height="23" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
ItemsSource="{Binding Source={StaticResource platforms}}"
ItemTemplate="{StaticResource platformTemplate}"/>
<Button Click="OnBuild" Grid.Row="1">Build...</Button>
<Button Click="OnTogglePC" Grid.Row="2">Toggle PC</Button>
</Grid>
</Window>
The code behind the XAML:
public partial class MainWindow : Window
{
ObservableCollection<Platform> m_platforms;
public MainWindow()
{
m_platforms = new ObservableCollection<Platform>();
m_platforms.Add(new Platform("PC"));
m_platforms.Add(new Platform("PS3"));
m_platforms.Add(new Platform("Xbox 360"));
InitializeComponent();
}
public ObservableCollection<Platform> Platforms
{
get { return m_platforms; }
set { m_platforms = value; }
}
private void OnBuild(object sender, RoutedEventArgs e)
{
string text = "";
foreach (Platform platform in m_platforms)
{
if (platform.Selected)
{
text += platform.Name + " ";
}
}
if (text == "")
{
text = "none";
}
MessageBox.Show(text, "WPF TEST");
}
private void OnTogglePC(object sender, RoutedEventArgs e)
{
m_platforms[0].Selected = !m_platforms[0].Selected;
}
}
...and finally the Platform code, enhanced to finish off the two way interaction:
public class Platform : INotifyPropertyChanged
{
private string m_name;
private bool m_selected;
public Platform(string name)
{
m_name = name;
m_selected = false;
}
public string Name
{
get { return m_name; }
set
{
m_name = value;
OnPropertyChanged("Name");
}
}
public bool Selected
{
get { return m_selected; }
set
{
m_selected = value;
OnPropertyChanged("Selected");
}
}
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Using DataContext, it gets even easier!
<Window x:Class="test5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:test5"
Title="MainWindow" Height="190" Width="177">
<Window.Resources>
<CollectionViewSource
Source="{Binding Path=.}"
x:Key="platforms"/>
<DataTemplate x:Key="platformTemplate" DataType="{x:Type custom:Platform}">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="1" IsChecked="{Binding Path=Selected}"/>
<TextBlock Margin="1" Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="23" />
<RowDefinition Height="23" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
ItemsSource="{Binding Source={StaticResource platforms}}"
ItemTemplate="{StaticResource platformTemplate}"/>
<Button Click="OnBuild" Grid.Row="1">Build...</Button>
<Button Click="OnTogglePC" Grid.Row="2">Toggle PC</Button>
</Grid>
</Window>
Here's the code behind this:
private ObservableCollection<Platform> m_platforms;
public MainWindow()
{
InitializeComponent();
m_platforms = new ObservableCollection<Platform>();
m_platforms.Add(new Platform("PC"));
m_platforms.Add(new Platform("PS3"));
m_platforms.Add(new Platform("Xbox 360"));
DataContext = m_platforms;
}
public void OnBuild(object sender, RoutedEventArgs e)
{
string text = "";
foreach (Platform platform in m_platforms)
{
if (platform.Selected)
{
text += platform.Name + " ";
}
}
if (text == "")
{
text = "none";
}
MessageBox.Show(text, "WPF TEST");
}
public void OnTogglePC(object sender, RoutedEventArgs e)
{
m_platforms[0].Selected = !m_platforms[0].Selected;
}
Note that I've dropped the need to declare Platforms as a property of the main window, instead I assign it to the DataContext, and the XAML source becomes, simply, "."

Categories