Prism's RegionManager.RequestNavigate is not working in WPF - c#

I am writing a small WPF application and using Prism 7.1. It seems that everything worked, but the only _regionManager.RequestNavigate() does not work. When I click a button which binding to DelegateCommand, _regionManager.RequestNavigate() was called but nothing happens. This is an image when I use an overload of RequestNavigate() which has a navigationCallback parameter, NavigationService is null:
_regionManager is assigned from the constructors.
This happens not only one place, all calls to _requestManager.RequestNavigate() still like that.
This is how I setup:
App.xaml.cs:
public partial class App: PrismApplication
{
protected override void OnStartup (StartupEventArgs e)
{
base.OnStartup (e);
}
protected override void RegisterTypes (IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<DangNhapView> ();
containerRegistry.RegisterSingleton<IBusyMonitor, CompositeMainComponentsBusyMonitor> ();
containerRegistry.RegisterSingleton<INetRequester, HttpNetService> ();
containerRegistry.RegisterSingleton<IInternetConnectionChecker, MNBConnectionChecker> ();
}
protected override Window CreateShell ()
{
return Container.Resolve<MainWindow> ();
}
protected override void ConfigureModuleCatalog (IModuleCatalog moduleCatalog) {
}
}
MainWindow.xaml:
<Window x:Class="XemDiemSinhVienMain.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"
xmlns:local="clr-namespace:XemDiemSinhVienMain.Views"
mc:Ignorable="d"
Title="Xem điểm sinh viên" Height="520" Width="940"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:uc="clr-namespace:XemDiemSinhVien.Infrastructures.UserControls;assembly=XemDiemSinhVien.Infrastructures"
xmlns:constants="clr-namespace:XemDiemSinhVien.Infrastructures.Constants;assembly=XemDiemSinhVien.Infrastructures"
xmlns:xemDiemSinhVienMain="clr-namespace:XemDiemSinhVienMain"
Icon="../app_icon.ico">
<WindowChrome.WindowChrome>
<WindowChrome GlassFrameThickness="1"
CornerRadius="0"
CaptionHeight="0"
UseAeroCaptionButtons="False"
ResizeBorderThickness="5"/>
</WindowChrome.WindowChrome>
<!-- https://stackoverflow.com/questions/2967218/window-out-of-the-screen-when-maximized-using-wpf-shell-integration-library/2975574 -->
<Window.Template>
<ControlTemplate TargetType="{x:Type local:MainWindow}">
<Border BorderBrush="Green">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}, Path=WindowState}" Value="Maximized">
<Setter Property="BorderThickness" Value="8"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<!-- Window's content -->
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentControl Grid.Row="1"
prism:RegionManager.RegionName="{x:Static constants:RegionNames.MainContentRegion}"/>
<Button Content="Switch to NavigationTestView"
Command="{Binding SwitchToNavigationTestViewCommand}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Margin="10"
Grid.Row="1"/>
<uc:WindowTitleBar Grid.Row="0"
MinimizeClick="WindowTitleBar_OnMinimizeClick"
CloseClick="WindowTitleBar_OnCloseClick"
MouseLeftButtonDown="WindowTitleBar_OnMouseLeftButtonDown"/>
</Grid>
</Border>
</ControlTemplate>
</Window.Template>
</Window>
MainWindowViewModel:
public class MainWindowViewModel : BindableBase
{
private IRegionManager _regionManager;
public DelegateCommand SwitchToNavigationTestViewCommand { get; private set; }
public MainWindowViewModel (IRegionManager regionManager) {
_regionManager = regionManager;
SwitchToNavigationTestViewCommand = new DelegateCommand (() => {
_regionManager.RequestNavigate (RegionNames.MainContentRegion, "DangNhapView");
});
}
}
So how can I fix this problem? Thank you for reading my question.

Related

Content is not showing

Unfortunately, I'm unable to understand where there is realization mistake.
The main application window should show a content from view model (HomeView.xaml)
Could you advice, what I has done incorrect?
Thanks in advance.
https://i.stack.imgur.com/mq3PS.png
MainWindow.xaml.cs
using ohb.MVVM.ViewModel;
namespace ohb
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new HomeViewModel();
}
// Moveable window
private void WindowMouseMoving(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
this.DragMove();
}
}
// Exit button
private void CloseButtonClick(object sender, RoutedEventArgs e)
{
Close();
}
}
}
MainWindow.xaml
<Window x:Class="ohb.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:ohb"
xmlns:viewModel="clr-namespace:ohb.MVVM.ViewModel"
xmlns:view="clr-namespace:ohb.MVVM.View"
mc:Ignorable="d"
Height="600" Width="920"
WindowStyle="None"
ResizeMode="NoResize"
Background="Transparent"
AllowsTransparency="True"
MouseMove="WindowMouseMoving">
<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>
<Border Background="#2f4f4f"
CornerRadius="30">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="75"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="Application"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Foreground="White"
FontSize="22"
Margin="20,0,0,0"/>
<StackPanel Grid.Row="1">
<RadioButton Content="Home"
Height="50"
Foreground="White"
FontSize="14"
Style="{StaticResource MenuButtonTheme}"
IsChecked="True"
Command="{Binding HomeViewCommand}"/>
<RadioButton Content="Menu"
Height="50"
Foreground="White"
FontSize="14"
Style="{StaticResource MenuButtonTheme}"
Command="{Binding DiscoveryViewCommand}"/>
<RadioButton Content="Settings"
Height="50"
Foreground="White"
FontSize="14"
Style="{StaticResource MenuButtonTheme}"/>
<RadioButton Content="Exit"
Click="CloseButtonClick"
Height="50"
Foreground="White"
FontSize="14"
Style="{StaticResource MenuButtonTheme}"/>
</StackPanel>
<TextBox Width="250"
Height="40"
VerticalContentAlignment="Center"
HorizontalAlignment="Left"
Margin="5"
Grid.Column="1"
Style="{StaticResource TextBoxTheme}"/>
<ContentControl Grid.Row="1"
Grid.Column="1"
Margin="10"
Content="{Binding CurrentView}"/>
</Grid>
</Border>
</Window>
App.xaml
<Application x:Class="ohb.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ohb"
xmlns:viewModel="clr-namespace:ohb.MVVM.ViewModel"
xmlns:view="clr-namespace:ohb.MVVM.View"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Theme/MenuButtonTheme.xaml"/>
<ResourceDictionary Source="Theme/TextBoxTheme.xaml"/>
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type viewModel:HomeViewModel}">
<view:HomeView/>
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
MenuButtonTheme.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}"
x:Key="MenuButtonTheme">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Grid VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Background="{TemplateBinding Background}">
<TextBlock Text="{TemplateBinding Property=Content}"
VerticalAlignment="Center"
Margin="50,0,0,0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="#3b6363"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
TextBoxTheme.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="{x:Type TextBox}"
x:Key="TextBoxTheme">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border CornerRadius="10"
Background="#3b6363"
Width="200" Height="40">
<Grid>
<Rectangle StrokeThickness="1"/>
<TextBox Margin="1"
BorderThickness="0"
Background="Transparent"
VerticalContentAlignment="Center"
Padding="5"
Foreground="White"
x:Name="SearchBox"/>
<TextBlock IsHitTestVisible="False"
Text="Search"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="10,0,0,0"
Foreground="DarkGray"
Grid.Column="1"
FontSize="12">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=SearchBox}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Hidden"/>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
ObservableObject.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ohb.Core
{
class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
if (PropertyChanged != null)
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
RelayCommand.cs
using System.Windows.Input;
namespace ohb.Core
{
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
}
MainViewModel.cs
using System;
using ohb.Core;
namespace ohb.MVVM.ViewModel
{
class MainViewModel : ObservableObject
{
public RelayCommand HomeViewCommand { get; set; }
public HomeViewModel HomeVM { get; set; }
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
HomeVM = new HomeViewModel();
CurrentView = HomeVM;
HomeViewCommand = new RelayCommand(o =>
{
CurrentView = HomeVM;
});
}
}
}
HomeViewModel.cs
using ohb.Core;
namespace ohb.MVVM.ViewModel
{
class HomeViewModel
{
}
}
HomeView.xaml
<UserControl x:Class="ohb.MVVM.View.HomeView"
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:viewModel="clr-namespace:ohb.MVVM.ViewModel"
xmlns:view="clr-namespace:ohb.MVVM.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<viewModel:HomeViewModel/>
</UserControl.DataContext>
<StackPanel>
<TextBlock Text="Discover"
Foreground="White"
FontSize="28"
HorizontalAlignment="Left"
Margin="0,0,0,20"/>
<StackPanel Orientation="Horizontal"
Margin="0,0,0,10">
<Border Width="400"
Height="200">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,2">
<GradientStop Color="#5bc3ff" Offset="0"/>
<GradientStop Color="#3aa0ff" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
<Border.Clip>
<RectangleGeometry RadiusX="10"
RadiusY="10"
Rect="0,0,400,200"/>
</Border.Clip>
<Grid>
<StackPanel>
<TextBlock Text="World leader
in global"
Foreground="White"
FontSize="28"
Margin="20,10,10,0"/>
<TextBlock Text="Get started"
Foreground="White"
FontSize="18"
Margin="20,10,10,0"/>
</StackPanel>
</Grid>
</Border>
<Border Width="200"
Height="200"
CornerRadius="10"
Margin="45,0,0,0">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,2">
<GradientStop Color="#5bc3ff" Offset="0"/>
<GradientStop Color="#3aa0ff" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
</Border>
</StackPanel>
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="Margin" Value="15,0,0,0"/>
</Style>
</StackPanel.Resources>
<TextBlock Text="most watched"
Foreground="White"
FontSize="20"
HorizontalAlignment="Left"
Margin="0,0,0,10"/>
<StackPanel Orientation="Horizontal">
<Border Width="150"
Height="150"
Background="#844eff"
CornerRadius="10"
Margin="0"/>
<Border Width="150"
Height="150"
Background="Red"
CornerRadius="10"/>
<Border Width="150"
Height="150"
Background="Gray"
CornerRadius="10"/>
<Border Width="150"
Height="150"
Background="Green"
CornerRadius="10"/>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>
A UserControl must not explictly set its own DataContext, as you have declared it in HomeView.xaml as
<UserControl.DataContext>
<viewModel:HomeViewModel/>
</UserControl.DataContext>
This assignment breaks the value inheritance of the DataContext property, which is essential to make the below DataTemplate work. Instead of getting the current data item as its DataContext, the control would always use the one that was explicitly set.
<DataTemplate DataType="{x:Type viewModel:HomeViewModel}">
<view:HomeView/>
</DataTemplate>
So simply remove the DataContext assignment from the UserControl's XAML.

binding value to template property

I have a serious problem on binding value to property.
This is my source code.
public partial class MainPage : Page,Autodesk.Revit.UI.IDockablePaneProvider
{
....
public System.Windows.Media.Brush BCol { get; set; }
.....
}
<ListBox Style = "{DynamicResource ListBoxStyle}">
...
</ListBox>
<Style x:Key="ListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Grid Background="{Binding BCol}">
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I want to bind BCol to grid background but it doesn't work.
How should I do?
Depending on where BCol is set, you might need to implement INotifyPropertyChanged interface. You also need to make sure that you are binding to the view model that is assigned to your DataContext.
You can also create a DependencyProperty. I have posted both solutions below. DependencyProperty one is probably the solution that you want.
INotifyPropertyChanged solution:
XAML
<Page x:Class="WpfTest.MainPage"
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:WpfTest"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="MainPage">
<Grid>
<Grid.Resources>
<Style x:Key="ListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Grid Background="{Binding BCol}">
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ListBox Style = "{DynamicResource ListBoxStyle}">
</ListBox>
</Grid>
</Page>
Code behind:
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfTest
{
/// <summary>
/// Interaction logic for MainPage.xaml
/// </summary>
public partial class MainPage : Page, INotifyPropertyChanged
{
private Brush bCol;
public System.Windows.Media.Brush BCol
{
get { return bCol; }
set
{
bCol = value;
RaisePropertyChanged("BCol");
}
}
public MainPage()
{
InitializeComponent();
DataContext = this; //This is a hack, you should really create a separate view model
BCol=new SolidColorBrush(Colors.Blue);
}
public event PropertyChangedEventHandler PropertyChanged= delegate { };
void RaisePropertyChanged(string name)
{
PropertyChanged(this,new PropertyChangedEventArgs(name));
}
}
}
DependencyProperty solution
XAML
<Page x:Class="WpfTest.MainPage"
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:WpfTest"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="MainPage">
<Grid>
<Grid.Resources>
<Style x:Key="ListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Grid Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:MainPage}}, Path=BCol}">
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ListBox Style = "{DynamicResource ListBoxStyle}">
</ListBox>
</Grid>
</Page>
Code behind
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfTest
{
/// <summary>
/// Interaction logic for MainPage.xaml
/// </summary>
public partial class MainPage : Page
{
private Brush bCol;
public static DependencyProperty BColProperty =DependencyProperty.Register("BCol", typeof(Brush),typeof(MainPage));
public Brush BCol
{
get { return (Brush)this.GetValue(BColProperty); }
set { this.SetValue(BColProperty, value); }
}
public MainPage()
{
InitializeComponent();
BCol=new SolidColorBrush(Colors.Blue);
}
}
}
Hope this helps.
you need to add this to the top of your xaml with all of your xmlns's
DataContext="{Binding RelativeSource={RelativeSource Self}}"

Visually report progres

So basically I want a control in which I add dynamically new lines, where every lines represent an operation that i'm doing. To achieve that i created a textbox like this one:
<TextBox Grid.Row="1" Text="{Binding CurrRow}" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" AcceptsReturn="True"/>
and I update the CurrRow property in my view model as following:
for (index = 0; index < 100; index++)
{
CurrRow = CurrRow + index.ToString();
//various operation
CurrRow = CurrRow + Environment.NewLine;
}
these is just an example to give the idea. The output is what I expected. However I would like something less "static" from visual perspective. For instance, i would like to add animated "..." within the line representing the operation that is currently in work, and i don't know if the TextBox is the right choose in this context. So my question is : How can i make a "report viewer" in WPF?
Here's a MVVM example using a few libraries I personally recommend :
View template (Xaml only):
<Window
x:Class="Sandbox.Test"
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:Sandbox"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Test"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ProgressBar.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Button.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.CheckBox.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ListBox.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.RadioButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBlock.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ToggleButton.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" Grid.Row="0">
<ItemsControl
MaxWidth="300"
Margin="16,8"
ItemsSource="{Binding LongTasks}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFinished}" Value="False">
<DataTrigger.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DockPanel Margin="12">
<ProgressBar
HorizontalAlignment="Center"
VerticalAlignment="Center"
DockPanel.Dock="Right"
Style="{StaticResource MaterialDesignCircularProgressBar}"
Value="{Binding Progress}" />
<TextBlock
VerticalAlignment="Center"
Style="{StaticResource MaterialDesignDisplay1TextBlock}"
Text="Task running" />
</DockPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock
Margin="12"
VerticalAlignment="Center"
Style="{StaticResource MaterialDesignDisplay1TextBlock}"
Text="Task finished" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Button
Grid.Row="1"
Margin="12"
HorizontalAlignment="Right"
Command="{Binding AddLongTask}"
Content="{materialDesign:PackIcon Kind=Plus,
Size=32}"
Style="{StaticResource MaterialDesignFloatingActionButton}" />
</Grid>
</Window>
```
Key point here you seem to want is to use a DataTrigger to change a ContentControl's ContentTemplate based on your condition so you can display something completely different.
The ViewModel to emulate the long run tasks in background :
using System;
using System.Reactive.Linq;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using System.Collections.ObjectModel;
namespace Sandbox
{
public class SandboxNotifiableViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged<TProperty>(Expression<Func<TProperty>> projection)
{
var memberExpression = (MemberExpression) projection.Body;
this.RaisePropertyChanged(memberExpression.Member.Name);
}
public void RaisePropertyChanged([CallerMemberName] string propertyName = "")
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class TestViewModel : SandboxNotifiableViewModel
{
private class SandBoxCommand : ICommand
{
private readonly Action cbk;
public event EventHandler CanExecuteChanged;
private void WarningRemover()
=> this.CanExecuteChanged?.Invoke(this, EventArgs.Empty);
public SandBoxCommand(Action cbk)
{
this.cbk = cbk;
}
public bool CanExecute(object parameter)
=> true;
public void Execute(object parameter)
=> this.cbk?.Invoke();
}
public TestViewModel()
{
this.AddLongTask = new SandBoxCommand(this.AddLongTaskAction);
this.LongTasks = new ObservableCollection<LongTaskViewModel>();
}
public ObservableCollection<LongTaskViewModel> LongTasks { get; }
private void AddLongTaskAction()
=> this.LongTasks.Add(new LongTaskViewModel());
public ICommand AddLongTask { get; }
}
public class LongTaskViewModel : SandboxNotifiableViewModel
{
private bool isFinished;
private int progress;
public LongTaskViewModel()
{
this.Progress = 0;
this.IsFinished = false;
// Refresh progress every 10ms 100 times
Observable.Interval(TimeSpan.FromMilliseconds(10))
.Select(x => x + 1) // 1 to 100
.Take(100)
// Here we make sure observable callback is called on dispatcher thread
.ObserveOnDispatcher()
.SubscribeOnDispatcher()
.Subscribe(this.OnProgressReported, this.OnLongTaskFinished);
}
public bool IsFinished
{
get => this.isFinished;
set
{
this.isFinished = value;
this.RaisePropertyChanged();
}
}
public int Progress
{
get => this.progress;
set
{
this.progress = value;
this.RaisePropertyChanged();
}
}
public void OnProgressReported(long dummyval)
{
this.Progress = (int) dummyval;
}
public void OnLongTaskFinished()
{
this.IsFinished = true;
}
}
}
I used Rx.NET to handle async notifications (here progress emulation) and MaterialDesignInXamlToolkit for the global styling

How do you get WPF validation to bubble up to a parent control?

So I have a control like this simplified version:
<local:ImageMapField x:Class="ImageApp.WPF.Controls.ImageMapContentField"
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:ImageApp.WPF.Controls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="Me">
<Grid HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" HorizontalAlignment="Stretch" Style="{DynamicResource BaseLabelStyle}">
<TextBlock Text="{Binding Header, RelativeSource={RelativeSource AncestorType=local:ImageMapContentField, Mode=FindAncestor}}" TextWrapping="WrapWithOverflow"></TextBlock>
</Label>
<StackPanel Grid.Column="1">
<Image />
<Border Margin="20,5,5,2">
<ContentPresenter Content="{Binding DataEntryContent, ElementName=Me}" />
</Border>
</StackPanel>
</Grid>
</local:ImageMapField>
And am using it like so:
<controls:ImageMapContentField Header="Foo Date"
FieldName="FooDate"
ImageSource="{Binding MyImage, Mode=TwoWay}"
ItemsSource="{Binding Map.Items}"
Zoom="{Binding MapFieldZoom}">
<controls:ImageMapContentField.DataEntryContent>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding MyDate, StringFormat=MM/dd/yyyy, ValidatesOnDataErrors=True, NotifyOnValidationError=True}">
<controls:WatermarkService.Watermark>
<TextBlock>Date</TextBlock>
</controls:WatermarkService.Watermark>
</TextBox>
<TextBox Grid.Column="1" Text="{Binding MyTime, StringFormat=HH\:mm}">
<controls:WatermarkService.Watermark>
<TextBlock>Time</TextBlock>
</controls:WatermarkService.Watermark>
</TextBox>
</Grid>
</controls:ImageMapContentField.DataEntryContent>
</controls:ImageMapContentField>
The problem is that because I am not binding my model's property to something on the ImageMapContentField, Validation.HasError on the ImageMapContentField is always false and never triggers.
What I get instead is the default TextBox validation.
What I really want is the ImageMapContentField to have a pink background. This works for my other controls where I am binding directly to something, but I cannot get this to work for the controls that have a ContentPresenter.
I am hoping I am just missing something that would allow the parent to capture the validation.
As requested here is a minimal example of the issue:
MainWindow.xaml
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="local:CustomTextField">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
<AdornedElementPlaceholder/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
<Setter Property="Background" Value="LightPink"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="local:CustomContentControl">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
<AdornedElementPlaceholder/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
<Setter Property="Background" Value="LightPink"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Window.DataContext>
<local:MyModel />
</Window.DataContext>
<StackPanel>
<local:CustomTextField LabelText="Number 1" Value="{Binding Number1, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True}" />
<local:CustomContentControl LabelText="Number 2">
<local:CustomContentControl.DataEntryContent>
<TextBox Text="{Binding Number2, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True}" />
</local:CustomContentControl.DataEntryContent>
</local:CustomContentControl>
</StackPanel>
</Window>
CustomTextField.xaml
<UserControl x:Class="WpfApp1.CustomTextField"
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:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="Me">
<StackPanel>
<Label Content="{Binding ElementName=Me, Path=LabelText}" />
<TextBox Text="{Binding ElementName=Me, Path=Value}" />
</StackPanel>
</UserControl>
CustomTextField.cs
public partial class CustomTextField : UserControl
{
public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register(
"LabelText", typeof(string), typeof(CustomTextField), new PropertyMetadata(default(string)));
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value", typeof(string), typeof(CustomTextField), new PropertyMetadata(default(string)));
public string Value
{
get { return (string) GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public string LabelText
{
get { return (string) GetValue(LabelTextProperty); }
set { SetValue(LabelTextProperty, value); }
}
public CustomTextField()
{
InitializeComponent();
}
}
CustomContentControl.xaml
<UserControl x:Class="WpfApp1.CustomContentControl"
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:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="Me">
<Grid>
<StackPanel>
<Label Content="{Binding ElementName=Me, Path=LabelText}" />
<ContentPresenter Content="{Binding DataEntryContent, ElementName=Me}" />
</StackPanel>
</Grid>
</UserControl>
CustomContentControl.cs
public partial class CustomContentControl : UserControl
{
public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register(
"LabelText", typeof(string), typeof(CustomContentControl), new PropertyMetadata(default(string)));
public static readonly DependencyProperty DataEntryContentProperty = DependencyProperty.Register(
"DataEntryContent", typeof(object), typeof(CustomContentControl), new PropertyMetadata(default(object)));
public object DataEntryContent
{
get { return (object) GetValue(DataEntryContentProperty); }
set { SetValue(DataEntryContentProperty, value); }
}
public string LabelText
{
get { return (string) GetValue(LabelTextProperty); }
set { SetValue(LabelTextProperty, value); }
}
public CustomContentControl()
{
InitializeComponent();
}
}
MyModel.cs
public class MyModel : INotifyPropertyChanged
{
int _number1;
int _number2;
public int Number1
{
get { return _number1; }
set
{
_number1 = value;
OnPropertyChanged();
}
}
public int Number2
{
get { return _number2; }
set
{
_number2 = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The WPF validation is already bubbling up to the parent control (even when the child control is inside a ContentPresenter) - Validation.ErrorEvent
The problem here is that even though the event bubbles up, the attached property Validation.HasError doesn't get updated - that's basically due to the fact that there is no error in the control's property bindings. And hence, you don't see the background change.
To rectify this - you can use this code:
Update style in MainWindow.xaml
<Style TargetType="local:CustomContentControl">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
<AdornedElementPlaceholder/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="HasErrors" Value="True">
<Setter Property="Background" Value="LightPink"/>
</Trigger>
</Style.Triggers>
</Style>
And, update CustomContentControl to add a HasErrors dependency property, and validation error event handler
public static readonly DependencyProperty HasErrorsProperty = DependencyProperty.Register("HasErrors", typeof(bool), typeof(CustomContentControl), new PropertyMetadata(false));
public bool HasErrors
{
get { return (bool)GetValue(HasErrorsProperty); }
set { SetValue(HasErrorsProperty, value); }
}
public CustomContentControl()
{
InitializeComponent();
Validation.AddErrorHandler(this, (s, args) => {
if (args.Action == ValidationErrorEventAction.Added)
{
this.ToolTip = args.Error.ErrorContent;
HasErrors = true;
}
else
{
this.ToolTip = null;
HasErrors = false;
}
});
}
And your background will be updated.

WPF Boolean Trigger

I have a boolean in the Window class of my WPF application. How can I target a trigger depending on whether this boolean is true or false?
<Grid>
<Grid.Triggers>
<Trigger ... />
</Grid.Triggers>
</Grid>
Thanks.
in *.cs file:
public partial class MainWindow : INotifyPropertyChanged
{
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public bool Flag { get; set; }
private void ButtonClick(object sender, RoutedEventArgs e)
{
Flag = true;
OnPropertyChanged("Flag");
}
protected void OnPropertyChanged(string property)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
in xaml form:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Window.Resources>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding Flag}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Click="ButtonClick" Content="Click Me" />
</Grid>
</Window>
You can use a DataTrigger. I think you need to use it within a style or template though.
Alternatively, you can capture the changes in the code behind.

Categories