Reputation: 59
I created a new C# WPF project based on https://www.codemag.com/Article/1905031/A-Design-Pattern-for-Building-WPF-Business-Applications-Part-1
with Caliburn Micro and Windsor Castle based on .NET 7.0 as target framework.
The Problem is: None of the Property Changes is updating the UI (The ugly gray InfoMessageArea should be hidden and a Text should appear while starting the App - as well as the WindowTitle that should reflect a change to "Test"). What am I missing here? The left list box correctly loads the implemented Modules using Windsor Castle, but the basic binding stuff is not working. And I would appreciate if anybody has an idea how the content of the window would "stretch" to full window's size as a side node.
I created a new ShellView, that is acting as the Main Window of the application.
The ShellViewModel derives from Conductor which has an ancestor Caliburn.Micro.PropertyChangedBase implementing INotifyPropertyChangedEx.
The ShellView.xaml code is as follows:
<Window x:Class="BM.App.Views.ShellView"
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:vm="clr-namespace:BM.App.ViewModels"
xmlns:local="clr-namespace:BM.App.Views"
mc:Ignorable="d"
d:DataContext="{x:Type vm:ShellViewModel}"
Title="{Binding WindowTitle}"
Height="450" Width="800"
WindowStartupLocation="CenterScreen"
Loaded="Window_Loaded">
<Window.Resources>
<vm:ShellViewModel x:Key="viewModel"
InfoMessageTitle="Please Wait While Loading Application..."
InfoMessage="Sample of Business Application Screens" />
</Window.Resources>
<Grid Style="{StaticResource contentAreaStyle}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<ListBox x:Name="MenuItems" DisplayMemberPath="Caption" Grid.Column="0"/>
<ContentControl x:Name="CurrentView" Grid.Row="0" Grid.Column="1" Margin="2"/>
<!-- Informational Message Area -->
<Border Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="1" Panel.ZIndex="2"
Visibility="{Binding Path=IsInfoMessageVisible, Converter={StaticResource visibilityConverter}}" Style="{StaticResource infoMessageArea}">
<StackPanel>
<TextBlock FontSize="20" Text="{Binding Path=InfoMessageTitle}" />
<TextBlock FontSize="12" Text="{Binding Path=InfoMessage}" />
</StackPanel>
</Border>
<StatusBar x:Name="stbMain" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" MinWidth="500"></StatusBar>
</Grid>
The ShellView.xaml.cs as follows:
public partial class ShellView : Window
{
// Main window's view model class
private ShellViewModel _viewModel;
public ShellView()
{
InitializeComponent();
// Connect to instance of the view model created by the XAML
_viewModel = (ShellViewModel)this.Resources["viewModel"];
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
await LoadApplication();
// Turn off informational message area
_viewModel.ClearInfoMessages();
}
public async Task LoadApplication()
{
// INFO: This text is not visible in the InfoMessage Area
_viewModel.InfoMessage = "Loading application (method 1)...";
//INFO: the 5 seconds delay are successfully invoked.
//TODO: Load Stuff...
await Dispatcher.BeginInvoke(new Action(() => {
System.Threading.Thread.Sleep(5000);
}), DispatcherPriority.Background);
// INFO: Test will not be visible in the application's window!
_viewModel.WindowTitle = "Test";
// INFO: Neither Refresh() is updating the UI.
_viewModel.Refresh();
await Task.CompletedTask;
}
}
The ShellViewModel.cs Code:
using BM.Contracts;
using Caliburn.Micro;
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace BM.App.ViewModels
{
public class ShellViewModel : Conductor<object>
{
public ObservableCollection<ViewModelBase> OpenTabs { get; private set; } = new ObservableCollection<ViewModelBase>();
public ObservableCollection<ShellMenuItem> MenuItems { get; private set; } = new ObservableCollection<ShellMenuItem>();
private const string WindowTitleDefault = "mp Beleg Matching (LF | DATEV | SB)";
private string _windowTitle = WindowTitleDefault;
public ShellViewModel()
{
MenuItems = new ObservableCollection<ShellMenuItem>();
AddTabCommand = null;
CloseTabCommand = null;
_selectedMenuItem = null;
}
ICommand AddTabCommand;
ICommand CloseTabCommand;
private bool _IsInfoMessageVisible = true;
private string _InfoMessageTitle = string.Empty;
private string _InfoMessage = string.Empty;
public bool IsInfoMessageVisible
{
get { return _IsInfoMessageVisible; }
set
{
_IsInfoMessageVisible = value;
NotifyOfPropertyChange("IsInfoMessageVisible");
}
}
public string InfoMessage
{
get { return _InfoMessage; }
set
{
_InfoMessage = value;
if (!_IsInfoMessageVisible && !string.IsNullOrEmpty(value))
{
IsInfoMessageVisible = true;
}
NotifyOfPropertyChange("InfoMessage");
}
}
public string InfoMessageTitle
{
get { return _InfoMessageTitle; }
set
{
_InfoMessageTitle = value;
NotifyOfPropertyChange("InfoMessageTitle");
}
}
public void ClearInfoMessages()
{
InfoMessage = string.Empty;
InfoMessageTitle = string.Empty;
IsInfoMessageVisible = false;
}
public string WindowTitle
{
get { return _windowTitle; }
set
{
_windowTitle = value;
NotifyOfPropertyChange(() => WindowTitle);
}
}
private ShellMenuItem _selectedMenuItem;
public ShellMenuItem SelectedMenuItem
{
get { return _selectedMenuItem; }
set
{
if (_selectedMenuItem == value)
return;
_selectedMenuItem = value;
NotifyOfPropertyChange(() => SelectedMenuItem);
NotifyOfPropertyChange(() => CurrentView);
}
}
public object? CurrentView
{
get { return _selectedMenuItem?.ScreenViewModel; }
}
}
}
Upvotes: 0
Views: 303
Reputation: 59
So, after investigating quite some time, I found the solution to my problem. Hope this will help some other desperate souls out there, having the same binding problems:
I added the DataContext attribute to the Grid of the ShellView's XAML:
<Grid Style="{StaticResource contentAreaStyle}"
DataContext="{StaticResource viewModel}">
and instantly the Binding was working. What I also figured out: The Intellisense (Jump to Declaration/F12) didn't work on my bound properties. After adding the DataContext to the Grid it "magically" worked out (eg. hitting F12 on IsInfoMessageVisible jumped to the definition of the ViewModel). I am not an expert in WPF, so I wouldn't have come to the idea to add the DataContext a second time to a child control a second time if I even had a DataContext bound to the Window's container. May be one of you have some ideas to fill the gap between my ears :-).
Upvotes: 0
Reputation: 17037
You do a mixture with code behind and MVVM. If you are using Caliburn, use MVVM. Castle Windsor is a container, so i dont know, i am using Ninject and Caliburn (last version).
Sample to display a splash screen during 3 sec and change the title of ShellViewModel: just create a WPF .NET project then:
Modify App.xaml:
<Application x:Class="WpfApp2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp2">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<local:Bootstrapper x:Key="Bootstrapper" />
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Bootsrapper.cs:
using Caliburn.Micro;
using Ninject;
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfApp2
{
public class Bootstrapper : BootstrapperBase
{
private IKernel kernel;
public Bootstrapper() //Constructor
{
Initialize();
}
protected override void Configure()
{
//Bind all views or tools to container
kernel = new StandardKernel();
kernel.Bind<IWindowManager>().To<WindowManager>().InSingletonScope();
kernel.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
kernel.Bind<SplashViewModel>().ToSelf().InSingletonScope();
kernel.Bind<ShellViewModel>().ToSelf().InSingletonScope();
}
//Override the startup method and launch the shell instead
protected override async void OnStartup(object sender, StartupEventArgs e)
{
var wm = kernel.Get<WindowManager>();
var vm = kernel.Get<SplashViewModel>();
//Load the splash screen
await wm.ShowWindowAsync(vm);
//Load the ShellView
await DisplayRootViewForAsync<ShellViewModel>();
}
protected override object GetInstance(Type service, string key)
{
return kernel.Get(service);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return kernel.GetAll(service);
}
}
}
SplashView.xaml:
<Window x:Class="WpfApp2.SplashView"
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:WpfApp2"
mc:Ignorable="d" WindowStartupLocation="CenterScreen" WindowStyle="None"
Title="SplashView" Height="100" Width="800">
<Grid>
<TextBox x:Name="Info" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
FontWeight="ExtraBold" FontSize="20" Foreground="Blue" IsReadOnly="True"/>
</Grid>
</Window>
SplashViewModel.cs
using Caliburn.Micro;
using System.Threading;
using System.Threading.Tasks;
namespace WpfApp2
{
public class SplashViewModel : Screen, IHandle<string>
{
public string Info { get; set; }
public SplashViewModel(IEventAggregator eventAggregator)
{
Info = "Loading application (method 1)...";
eventAggregator.SubscribeOnPublishedThread(this);
}
public async Task HandleAsync(string message, CancellationToken cancellationToken)
{
//Message received, wait 3 sec before to close the Splash Screen
await Task.Delay(3000);
if(message.Equals("End"))
await TryCloseAsync();
}
}
}
ShellView.xaml
<Window x:Class="WpfApp2.ShellView"
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:WpfApp2"
mc:Ignorable="d" WindowStartupLocation="CenterScreen"
Height="450" Width="800">
<Grid>
<TextBox Name="Info2" Width="300" Height="70" Background="Aquamarine" />
</Grid>
</Window>
And finally ShellViewModel.cs
using Caliburn.Micro;
using System.Threading;
using System.Threading.Tasks;
namespace WpfApp2
{
public class ShellViewModel : Screen
{
public IEventAggregator eventAggregator;
public ShellViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
//Change the title of view
DisplayName = "TEST";
Info2 = $"View {DisplayName} Loaded!!";
}
protected override async Task OnInitializeAsync(CancellationToken c)
{
//Publish a string message to close the Splash Screen
await eventAggregator.PublishOnUIThreadAsync("End");
}
private string info2;
public string Info2
{
get
{
return info2;
}
set
{
info2 = value;
NotifyOfPropertyChange(() => Info2);
}
}
}
}
Upvotes: 0