WindowChrome causing issues with view ContentControl binding? - c#

I've been working on a small tool for ISO integration and I decided to use WPF for the UI. I'm also doing my best to follow the MVVM pattern for future projects. For the main window, I decided to use WindowChrome so I could keep resizing with a restyled window, however, when I try to select a different view, it causes a exception. I tested it and it does work fine, but as soon as I try to bind the content to the templates content, it seems to cause issues with changing the current view.
Main window xaml:
<WindowChrome.WindowChrome>
<WindowChrome ResizeBorderThickness="{Binding ResizeBorderThickness}"
CaptionHeight="{Binding TitleHeight}" GlassFrameThickness="0"/>
</WindowChrome.WindowChrome>
<Window.Resources>
<Style TargetType="{x:Type local:OSToolWPF}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border Padding="{Binding OuterMarginThickness}">
<Grid>
<Border CornerRadius="{Binding WindowCornerRadius}"
Background="{StaticResource BackgroundLightBrush}">
<Border.Effect>
<DropShadowEffect ShadowDepth="0" Opacity=".2"/>
</Border.Effect>
</Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding TitleHeightGridLength, FallbackValue=50}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border CornerRadius="10 10 0 0"
Background="{StaticResource BackgroundDarkBrush}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Viewbox Margin="25 0 0 0" HorizontalAlignment="Left">
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Title}"/>
</Viewbox>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Style="{StaticResource ButtonBase}" Command="{Binding Minimize}" Width="50">
<Image Style="{StaticResource ControlImage}" Source="/Assets/Top/Minimize.png" Stretch="None"/>
</Button>
<Button Style="{StaticResource ButtonBase}" Command="{Binding Maximize}" Width="50">
<Image Style="{StaticResource ControlImage}" Source="/Assets/Top/Windowed.png" Stretch="None"/>
</Button>
<Button Style="{StaticResource ButtonBase}" Command="{Binding Close}" Width="50">
<Image Style="{StaticResource ControlImage}" Source="/Assets/Top/Close.png" Stretch="None"/>
</Button>
</StackPanel>
</Grid>
</Border>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel>
<Button Style="{StaticResource ButtonBase}" Tag="Registry" Command="{Binding Menu}"
Content="Registry" FontSize="20" Height="50" CommandParameter="{Binding
RelativeSource={RelativeSource Mode=Self}, Path=Tag}"/>
<Button Style="{StaticResource ButtonBase}" Tag="Services" Command="{Binding Menu}"
Content="Services" FontSize="20" Height="50" CommandParameter="{Binding
RelativeSource={RelativeSource Mode=Self}, Path=Tag}"/>
<Button Style="{StaticResource ButtonBase}" Tag="Visuals" Command="{Binding Menu}"
Content="Visuals" FontSize="20" Height="50" CommandParameter="{Binding
RelativeSource={RelativeSource Mode=Self}, Path=Tag}"/>
</StackPanel>
<Button Grid.Row="2" Style="{StaticResource ButtonBase}"
Content="Exit" FontSize="20" Command="{Binding Close}"
Height="50"/>
</Grid>
<Border Grid.Column="1">
<ContentControl Content="{TemplateBinding Content}"/>
<Border/>
</Grid>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ContentControl Content="{Binding CurrentView}"/>
Main window view model (the data binding is done in xaml.cs since it needs a window):
public class OSToolViewModel : BaseViewModel
{
#region Private
private Window wWindow;
private object wCurrentView;
private int wOuterMarginSize = 10;
private int wWindowRadius = 15;
#endregion
public OSToolViewModel(Window window)
{
wWindow = window;
wWindow.StateChanged += (sender, e) =>
{
OnPropertyChanged(nameof(ResizeBorderThickness));
OnPropertyChanged(nameof(OuterMarginSize));
OnPropertyChanged(nameof(OuterMarginThickness));
OnPropertyChanged(nameof(WindowRadius));
OnPropertyChanged(nameof(WindowCornerRadius));
};
Menu = new BaseCommand(e => CurrentView = new ExpanderItemsList());
Minimize = new BaseCommand(e => wWindow.WindowState = WindowState.Minimized);
Maximize = new BaseCommand(e => wWindow.WindowState ^= WindowState.Maximized);
Close = new BaseCommand(e => CloseMessage());
}
#region Voids
private void CloseMessage()
{
if (DialogBox.Show("Would you like to exit?", "", wWindow.Title, DialogBoxButtons.YesNo) == DialogResult.Yes)
{
wWindow.Close();
}
}
#endregion
#region Commands
public BaseCommand Menu { get; set; }
public BaseCommand Minimize { get; set; }
public BaseCommand Maximize { get; set; }
public BaseCommand Close { get; set; }
#endregion
#region Public
public object CurrentView
{
get { return wCurrentView; }
set
{
if (value == wCurrentView)
return;
wCurrentView = value;
OnPropertyChanged(nameof(CurrentView));
}
}
public int ResizeBorder { get; set; } = 6;
public int OuterMarginSize
{
get
{
return wWindow.WindowState == WindowState.Maximized ? 0 : wOuterMarginSize;
}
set
{
wOuterMarginSize = value;
}
}
public int WindowRadius
{
get
{
return wWindow.WindowState == WindowState.Maximized ? 0 : wWindowRadius;
}
set
{
wWindowRadius = value;
}
}
public double TitleHeight { get; set; } = 50;
public Thickness ResizeBorderThickness { get { return new Thickness(ResizeBorder + OuterMarginSize); } }
public Thickness OuterMarginThickness { get { return new Thickness(OuterMarginSize); } }
public CornerRadius WindowCornerRadius { get { return new CornerRadius(WindowRadius); } }
public GridLength TitleHeightGridLength { get { return new GridLength(TitleHeight + ResizeBorder); } }
#endregion
}
The view I'm trying to bind to is a page, so I'm not sure if that would cause issues or not, but I've tried a lot of different combos and the combo I shared causes a exception. If I didn't bind to to the templated content, it would work fine. I'm baffled as to why this happens, and as a side note, the buttons in the window aren't cornered like they should be, so if you have a solution for this too, that would be much appreciated! Thank you and I hope you enjoy your day!

I just realized this issue was because of the fact I was using a Page rather than a UserControl. Switching the view to a UserControl solved the issue. Hopefully this helps if anyone else runs into this issue.

Related

c# MVVM: How to bind 2 ContentControls to 2 Properties of MainViewModel

I'm working on a daily task manager.
I want to display 2 different views inside of 2 different ContentControls in my MainWindow.xaml.
I have a ViewModelMain, which contains two properties of type ViewModelBase. So I can switch between different ViewModels, because they all implement the ViewModelBase, which implements INotifyPropertyChanged. With just one ContentControl it is no Problem to choose any view. In fact it is possible (when configured in the Command) to select the viewmodels from the second ContentControl correctly.
Now I need help... why is my second ContentControl not displaying anything?
I've tried to evaluate, where the problem is, but the ViewModel selection is passed as expected and the OnPropertyChanged Method is called.
Thanks for your help.
My ViewModelMain:
public class ViewModelMain : ViewModelBase {
private ViewModelBase selectedVMMain;
private ViewModelBase selectedVMEssentail;
public ViewModelBase SelectedVMMain {
get { return selectedVMMain; }
set {
if( selectedVMMain != value ) {
selectedVMMain = value;
OnPropertyChanged(nameof(selectedVMMain));
}
}
}
public ViewModelBase SelectedVMEssential {
get { return selectedVMEssentail; }
set {
if( selectedVMEssentail != value ) {
selectedVMEssentail = value;
OnPropertyChanged(nameof(selectedVMEssentail));
}
}
}
public ICommand CommandUpdateView { get; set; }
public ViewModelMain() {
this.CommandUpdateView = new CommandUpdateView(this);
}
}
CommandUpdateView:
private ViewModelMain viewModelMain;
public event EventHandler CanExecuteChanged;
public CommandUpdateView( ViewModelMain _viewModelMain) {
this.viewModelMain = _viewModelMain;
}
public bool CanExecute( object parameter ) => true;
public void Execute( object parameter ) {
switch( parameter ) {
case nameof(EnumViewModels.Übersicht):
viewModelMain.SelectedVMMain = new ViewModelÜbersicht();
break;
case nameof(EnumViewModels.Statistik):
viewModelMain.SelectedVMMain = new ViewModelStatistik();
break;
case nameof(EnumViewModels.Kategorie):
viewModelMain.SelectedVMEssential = new ViewModelKategorie();
break;
case nameof(EnumViewModels.Projekt):
viewModelMain.SelectedVMEssential = new ViewModelProjekt();
break;
case nameof(EnumViewModels.Aufgabe):
viewModelMain.SelectedVMEssential = new ViewModelAufgabe();
break;
case nameof(EnumViewModels.Pomodoro):
viewModelMain.SelectedVMEssential = new ViewModelPomodoro();
break;
default:
viewModelMain.SelectedVMMain = new ViewModelTEST();
viewModelMain.SelectedVMEssential = new ViewModelTEST();
break;
}
}
}
MainWindow.xaml (DataContext is set to new ViewModelMain)
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="100" Width="Auto"/>
<ColumnDefinition MinWidth="200" Width="Auto"/>
<ColumnDefinition MinWidth="200" Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Top"
Grid.Column="0" Grid.Row="0"
Margin="5" Width="100" >
<Button x:Name="Button_Uebersicht" Content="Übersicht"
Command="{Binding CommandUpdateView}" CommandParameter="Übersicht"
HorizontalAlignment="Stretch" VerticalAlignment="Center"
Margin="0,5" />
<Button x:Name="Button_Statistik" Content="Statistik"
Command="{Binding CommandUpdateView}" CommandParameter="Statistik"
HorizontalAlignment="Stretch" VerticalAlignment="Center"
Margin="0,5" />
<Button x:Name="Button_Kategorie" Content="Kategorie"
Command="{Binding CommandUpdateView}" CommandParameter="Kategorie"
HorizontalAlignment="Stretch" VerticalAlignment="Center"
Margin="0,5" />
<Button x:Name="Button_Projekt" Content="Projekt"
Command="{Binding CommandUpdateView}" CommandParameter="Projekt"
HorizontalAlignment="Stretch" VerticalAlignment="Center"
Margin="0,5" />
<Button x:Name="Button_Aufgabe" Content="Aufgabe"
Command="{Binding CommandUpdateView}" CommandParameter="Aufgabe"
HorizontalAlignment="Stretch" VerticalAlignment="Center"
Margin="0,5" />
<Button x:Name="Button_Pomodoro" Content="Pomodoro"
Command="{Binding CommandUpdateView}" CommandParameter="Pomodoro"
HorizontalAlignment="Stretch" VerticalAlignment="Center"
Margin="0,5" />
<Button x:Name="Button_TEST" Content="TEST"
Command="{Binding CommandUpdateView}"
HorizontalAlignment="Stretch" VerticalAlignment="Center"
Margin="0,5" />
</StackPanel>
<ContentControl x:Name="CC_Main"
Content="{Binding SelectedVMMain}"
Grid.Column="1" Grid.Row="0"
Margin="5" Width="Auto"/>
<ContentControl x:Name="CC_Essential"
Content="{Binding SelectedVMEssential}"
Grid.Column="2" Grid.Row="0"
Margin="5" Width="Auto"/>
SOLVED
It was the field, which was misspelled... the Property was named correctly, but the the Binding didn't work because, i used the nameof(field) keyword...
Thanks for the nice introduction on Stack Overflow!!!

Background image not being shown unless main window is active

I have an annoying bug that I can't seem to figure out and it is driving me crazy. I have a window with a listbox in it and when an item is selected an new window is opened using mvvm light which displays different details, the problem is when I click on the item in my listbox (BrowseGamesView) the new window that opens (GameDetailsView) does not show the background image unless I click back to the BrowseGamesView. I'm calling to an api and using a converter to build the url for the image. I need the image to show when the new window opens.
GameDetailsView
Where I need the background image to show when the window opens.
<Window.Resources>
<vm:GameDetailsViewModel x:Key="vm"/>
<converters:StringToBackgroundImageConverter x:Key="stringToBackgroundImageConverter"/>
<converters:StringToImageConverter x:Key="stringToImageConverter"/>
</Window.Resources>
<Grid DataContext="{StaticResource vm}">
<Grid.Background>
<ImageBrush ImageSource="{Binding Game.ScreenshotBackground, Converter={StaticResource stringToBackgroundImageConverter}}"/>
</Grid.Background>
</Grid>
BrowseGamesView
Where the listbox is held.
<ListBox ItemsSource="{Binding Games}"
ItemContainerStyle="{DynamicResource ListBoxItemStyle1}"
SelectedItem="{Binding SelectedGame}"
Width="480"
Height="500"
Grid.Row="4">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="{Binding cover.image_id, Converter={StaticResource stringToImage}}"
Stretch="Fill"
Width="100"
Height="130"/>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="310"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding name}"
Foreground="White"
FontWeight="Bold"
FontSize="16"
Margin="15 2 0 0"
TextWrapping="Wrap"/>
<TextBlock Text="{Binding aggregated_rating, StringFormat={}{0:F0}, Converter={StaticResource nullRatingConverter}}"
Grid.Column="1"
Foreground="{Binding AggregatedRatingColor}"
FontWeight="Bold"
FontSize="16"
HorizontalAlignment="Center"/>
</Grid>
<ItemsControl Grid.Row="1"
ItemsSource="{Binding DeveloperCompanies}"
Margin="15 2 0 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="commaTextBlock"
Text=", "
Foreground="White"/>
<TextBlock Text="{Binding company.name}"
Foreground="White"
TextWrapping="Wrap"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}" Value="{x:Null}">
<Setter Property="Visibility" TargetName="commaTextBlock" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid Grid.Row="2"
VerticalAlignment="Bottom"
Margin="0 0 0 4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Release Date:"
Foreground="White"
Margin="15 20 0 0"/>
<TextBlock Text="{Binding first_release_date, Converter={StaticResource unixTimestampToDateTimeConverter}}"
Foreground="White"
Margin="10 20 0 0"
Grid.Column="1"/>
</Grid>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
GameDetailsViewModel
public class GameDetailsViewModel : INotifyPropertyChanged
{
private Game game;
public Game Game
{
get { return game; }
set
{
game = value;
OnPropertyChanged("Game");
}
}
public GameDetailsViewModel()
{
Messenger.Default.Register<Game>(this, NotifyMe);
}
public void NotifyMe(Game sentGame)
{
Game = sentGame;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
BrowseGamesViewModel
public class BrowseGamesViewModel : ViewModelBase, INotifyPropertyChanged
{
private string query;
private Game selectedGame;
private int gamesCount;
private Game game;
public RelayCommand ShowGameDetailsViewCommand { private set; get; }
public string Query
{
get { return query; }
set
{
query = value;
OnPropertyChanged("Query");
}
}
public Game Game
{
get { return game; }
set
{
game = value;
OnPropertyChanged("Game");
}
}
public Game SelectedGame
{
get { return selectedGame; }
set
{
selectedGame = value;
OnPropertyChanged("SelectedGame");
GetGameDetails();
ShowGameDetailsViewCommandExecute();
}
}
public ObservableCollection<Game> Games { get; set; }
public int GamesCount
{
get { return gamesCount; }
set
{
gamesCount = value;
OnPropertyChanged("GamesCount");
}
}
public SearchGamesCommand SearchGamesCommand { get; set; }
public BrowseGamesViewModel()
{
Games = new ObservableCollection<Game>();
SearchGamesCommand = new SearchGamesCommand(this);
ShowGameDetailsViewCommand = new RelayCommand(ShowGameDetailsViewCommandExecute);
}
public void ShowGameDetailsViewCommandExecute()
{
Messenger.Default.Send(new NotificationMessage("ShowGameDetailsView"));
}
private async void GetGameDetails()
{
Game = await IGDBHelper.GetGameInformation(SelectedGame.id);
Messenger.Default.Send(Game);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
BrowseGamesView Code Behind
public partial class BrowseGamesView : Window
{
public BrowseGamesView()
{
InitializeComponent();
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
}
private void NotificationMessageReceived(NotificationMessage msg)
{
if (msg.Notification == "ShowGameDetailsView")
{
var view = new GameDetailsView();
view.Show();
}
}
}

How to open new modal dialog using prism in wpf

I have done opening the modal dialog in Wpf MVVM. But I am new to prism. I have tried the following code in MVVM. Now its working fine. But I want to do the same concept using prism with ribbon window. In the below code I have not used ribbon window, instead of ribbon window I used the button. please refer the below code,
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type vm:ModalDialogViewModel}">
<view:ModalDialog />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center">
<Grid>
<Button
Width="150"
Height="25"
Margin="0,10,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Command="{Binding Path=LoadViewCommand}"
Content="Show Modal Dialog" />
</Grid>
</Grid>
<Grid Visibility="{Binding IsCloseModalWindow, Converter={StaticResource BooleanToVisibilityConverter}}">
<Border Background="#90000000">
<Border
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="White"
BorderBrush="Transparent"
BorderThickness="0"
CornerRadius="0">
<Border.BitmapEffect>
<DropShadowBitmapEffect
Direction="270"
Opacity="0.5"
ShadowDepth="0.7"
Color="Black" />
</Border.BitmapEffect>
<ContentControl Content="{Binding Path=CurrentViewModel}" />
</Border>
</Border>
</Grid>
</Grid>
MainWindowViewModel
public class MainWindowViewModel : ViewModelBase, ICloseWindow
{
private ICommand loadViewCommand;
private ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel
{
get { return _currentViewModel; }
set
{
_currentViewModel = value;
this.OnPropertyChanged("CurrentViewModel");
}
}
private bool isCloseModalWindow = false;
public bool IsCloseModalWindow
{
get { return isCloseModalWindow; }
set { isCloseModalWindow = value; OnPropertyChanged("IsCloseModalWindow"); }
}
public MainWindowViewModel()
{
}
public ICommand LoadViewCommand => loadViewCommand ?? (loadViewCommand = new RelayCommand(showView, canShowView));
private void showView(object obj)
{
IsCloseModalWindow = true;
CurrentViewModel = new ModalDialogViewModel(new ModalDialogModel() { Name = "New Modal Window" }, this);
}
private bool canShowView(object obj)
{
return true;
}
public void closeWindow()
{
IsCloseModalWindow = false;
}
}
ModalDialog
<Grid MinWidth="300" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid
Grid.Row="0"
MinHeight="30"
Background="SkyBlue">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="1"
Width="40"
Height="25"
Content="X"
BorderThickness="0"
Background="Transparent"
Foreground="White"
Margin="0,0,5,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding CancelCommand}"
CommandParameter="{Binding ElementName=modalDialog}"
ToolTip="Close" />
</Grid>
</Grid>
<Grid Grid.Row="1" Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
Grid.Row="0"
Grid.Column="0"
Content="Name"
Style="{StaticResource commonMargin}" />
<Label
Grid.Row="0"
Grid.Column="1"
Content=":"
Style="{StaticResource commonMargin}" />
<TextBox
Grid.Row="0"
Grid.Column="2"
Width="100"
Text="{Binding Path=Name}"
Style="{StaticResource commonTimerMargin}" />
</Grid>
<Grid
Grid.Row="2"
Margin="0,0,15,10"
HorizontalAlignment="Right">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="0"
Width="80"
Height="30"
Margin="0,0,10,0"
Background="White"
Command="{Binding CancelCommand}"
CommandParameter="{Binding ElementName=modalDialog}"
Content="CANCEL" />
<Button
Grid.Column="1"
Width="80"
Height="30"
Background="SkyBlue"
Command="{Binding ApplyCommand}"
CommandParameter="{Binding ElementName=modalDialog}"
Content="APPLY"
Foreground="White" />
</Grid>
</Grid>
ModalDialogViewModel
public class ModalDialogViewModel : ViewModelBase
{
private ICommand cancelCommand;
public ModalDialogModel Model { get; private set; }
private ICloseWindow _closeWindow;
public ModalDialogViewModel(ModalDialogModel modalDialogModel, ICloseWindow closeWindow)
{
this.Model = modalDialogModel;
this._closeWindow = closeWindow;
}
public ICommand CancelCommand => cancelCommand ?? (cancelCommand = new RelayCommand(CloseWindow, CanCloseWindow));
private void CloseWindow(object obj)
{
_closeWindow.closeWindow();
}
private bool CanCloseWindow(object obj)
{
return true;
}
}
My Requirement is when user click the ribbon window button the modal dialog is open. The Shell window is a ribbon window. I have added HomeTab module and many other modules as separate class library.
You can do it like this in .xaml:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:prism="http://prismlibrary.com/"
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding PopUpDialogActionBinding}"> '<-- Here is your action binding
<prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="False">
<prism:PopupWindowAction.WindowStyle>
<Style TargetType="Window">
<Setter Property="Icon" Value="IconPath"/>
<Setter Property="Height" Value="400"/>
<Setter Property="Width" Value="400"/>
</Style>
</prism:PopupWindowAction.WindowStyle>
<prism:PopupWindowAction.WindowContent>
<views:YourCustomView /> ' <--- Put your view into the dialog
</prism:PopupWindowAction.WindowContent>
</prism:PopupWindowAction>
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
<Grid>
...
</Grid>
I did it as in the example 28:
https://github.com/PrismLibrary/Prism-Samples-Wpf

How to display user control within the main window in WPF using MVVM

I have a WPF application and just embarked in learning MVVM pattern.
My goal is that in my application the main window has a button. When this button is clicked, another window (or user control) will appear on top of the main window.
This is the code of MainWindow.xaml
<Window x:Class="SmartPole1080.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utilities="clr-namespace:SoltaLabs.Avalon.Core.Utilities;assembly=SoltaLabs.Avalon.Core"
xmlns:userControls="clr-namespace:SoltaLabs.Avalon.View.Core.UserControls;assembly=SoltaLabs.Avalon.View.Core"
xmlns:controls="clr-namespace:WpfKb.Controls;assembly=SmartPole.WpfKb"
xmlns:wpf="clr-namespace:WebEye.Controls.Wpf;assembly=WebEye.Controls.Wpf.WebCameraControl"
xmlns:view="clr-namespace:SmartPole.View;assembly=SmartPole.View"
xmlns:view1="clr-namespace:SmartPole1080.View"
xmlns:behaviors="clr-namespace:SoltaLabs.Avalon.Core.Behaviors;assembly=SoltaLabs.Avalon.Core"
Title="Smart Pole"
WindowStartupLocation="CenterScreen"
Name="mainWindow"
behaviors:IdleBehavior.IsAutoReset="True" WindowState="Maximized" WindowStyle="None">
<Canvas Background="DarkGray">
<!--Main Grid-->
<Grid Width="1080" Height="1920" Background="White" Name="MainGrid"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Background="Black">
<Grid Background="#253341">
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="264"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<Button Tag="{StaticResource EmergencyImg}" Name="EmergencyButton"
Command="{Binding ShowEmergencyPanel}">
<Image Source="{StaticResource EmergencyImg}" />
</Button>
</Grid>
<!--Emergency Window Dialog-->
<Grid Name="EmergencyPanel">
<view1:EmergencyInfo x:Name="EmergencyInfoPanel"/>
</Grid>
</Grid>
</StackPanel>
</Grid>
<!--Main Grid-->
</Canvas>
This is the other window (user control - EmergencyInfo.xaml)
<UserControl x:Class="SmartPole1080.View.EmergencyInfo"
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:SmartPole1080.View"
mc:Ignorable="d"
d:DesignHeight="1920" d:DesignWidth="1050"
x:Name="EmergencyInfoPanel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" BorderBrush="White" Background="White">
<Button Background="White" BorderThickness="0" FontWeight="Bold" Foreground="Red"
HorizontalAlignment="Right" FontSize="25" Margin="0,0,15,0"
Command="{Binding HideEmergencyPanel}">
close X
</Button>
</Border>
<Image Grid.Row="1" Source="{StaticResource EdenParkInfoImg}" HorizontalAlignment="Left" />
</Grid>
I want to implement this behavior using an MVVM pattern. I have set the binding ShowEmergencyPanel in button EmergencyButton to show EmergencyInfo when this button is click.
Any help is greatly appreciated.
Why dont you make navigation, something like this. Make section for conetent that will be injected, and whatever type of object you are expecting put it in Windows.Resources in DataTemplate.
In main windo xaml
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type other:OtherViewModel}">
<other:OtherView />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid x:Name="Navigation">
<StackPanel Orientation="Horizontal">
<Button x:Name="HomeView"
Content="Home"
Margin="5"
Command="{Binding NavigationCommand}"
CommandParameter="home" />
<Button x:Name="Menu"
Content="OtherView"
Margin="5"
Command="{Binding NavigationCommand}"
CommandParameter="Other" />
</StackPanel>
</Grid>
<Grid x:Name="MainContent"
Grid.Row="1">
<ContentControl Content="{Binding CurrentViewModel}" />
</Grid>
</Grid>
MainWindowViewModel can look something like this.
public class MainWindowViewModel : INotifyPropertyChanged
{
private OtherViewModel otherVM;
private HomeViewModel homeVM;
public DelegateCommand<string> NavigationCommand { get; private set; }
public MainWindowViewModel()
{
otherVM = new OtherViewModel();
homeVM = new HomeViewModel();
// Setting default: homeViewModela.
CurrentViewModel = homeVM;
NavigationCommand = new DelegateCommand<string>(OnNavigate);
}
private void OnNavigate(string navPath)
{
switch (navPath)
{
case "other":
CurrentViewModel = otherVM;
break;
case "home":
CurrentViewModel = homeVM;
break;
}
}
private object _currentViewModel;
public object CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel != value)
{
_currentViewModel = value;
OnPropertyChanged();
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged(this, new propertyChangedEventArgs(propertyName));
}
#endregion
}
Where DelegateCommand you can make yours,check how to make RelayCommand(and generic one) or use PRISM that have it's own DelegateCommand. But if you want to use PRISM, it allready has navigation to regions, that can solve many problems. Check videos from Brian Lagunas.
EDIT:
This is to show/hide grid. In your mainWindow where you set that EmergencyInfo u can show/hide that grid this way.
in your MainViewViewModel make a bool property IsVisible
private bool _isVisible;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
OnPropertyChanged();
}
}
in your MainView.Resources set key to BooleanToVisibilityConverter
something like:
<BooleanToVisibilityConverter x:Key="Show"/>
and your grid that you want to show/hide set visibility:
<Grid x:Name="SomeGridName"
Grid.Row="1"
Grid.Colum="1"
Visibility="{Binding IsVisible,Converter={StaticResource Show}}">
And finally set that IsVisible property to some ToggleButton, just to switch between true/false
<ToggleButton IsChecked="{Binding IsVisible}"
Content="ShowGrid" />
This way, you show/hide that grid part based on IsVisible, and you control that visibility onClick. Hope that helps.
Inside your Window xaml:
<ContentPresenter Content="{Binding Control}"></ContentPresenter>
In this way, your ViewModel must contain a Control property.
Add your ViewModel to DataContext of Window.
(For example in window constructor, this.Datacontext = new ViewModel();)
Or another way with interfaces:
public interface IWindowView
{
IUserControlKeeper ViewModel { get; set; }
}
public interface IUserControlKeeper
{
UserControl Control { get; set; }
}
public partial class YourWindow : IViewWindow
{
public YourWindow()
{
InitializeComponent();
}
public IUserControlKeeper ViewModel
{
get
{
return (IUserControlKeeper)DataContext;
}
set
{
DataContext = value;
}
}
}
(In this way, initialize your window where you want to use. Service?)
private IViewWindow _window;
private IViewWindow Window //or public...
{
get{
if(_window==null)
{
_window = new YourWindow();
_window.ViewModel = new YourViewModel();
}
return _window;
}
}
Open your window with one of your UserControls:
void ShowWindowWithControl(object ControlView, INotifyPropertyChanged ControlViewModel, bool ShowAsDialog)
{
if(ControlView is UserControl)
{ //with interface: Window.ViewModel.Control = ...
(Window.DataContext as YourViewModel).Control = (UserControl)ControlView;
if (ControlViewModel != null)
(Window.DataContext as YourViewModel).Control.DataContext = ControlViewModel;
if (ShowAsDialog) //with interface use cast to call the show...
Window.ShowDialog();
else
Window.Show();
}
}
What you can do is, place the Emergency usercontrol inside MainGrid and control its visibility through Model property.
<Canvas Background="DarkGray">
<!--Main Grid-->
<Grid Width="1080" Height="1920" Background="White" Name="MainGrid"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Background="Black">
<Grid Background="#253341">
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="264"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<Button Tag="{StaticResource EmergencyImg}" Name="EmergencyButton"
Command="{Binding ShowEmergencyPanel}">
<Image Source="{StaticResource EmergencyImg}" />
</Button>
</Grid>
</Grid>
</StackPanel>
<Grid Name="EmergencyPanel">
<view1:EmergencyInfo x:Name="EmergencyInfoPanel" Visibility={Binding IsEmergencyPanelVisible,Converter={StaticResource BoolToVisibilityConverter}} DataContext={Binding} />
</Grid>
</Grid>
<!--Main Grid-->
</Canvas>
By setting proper background to Grid that holding usercontrol, you can achieve modal window like effect.
And you need to have IsEmergencyPanelVisible(default value is false) is your Model, and this property should be changed to true on your button click command.
Note : I guess you are familiar with converters, so i included Converters in my solution.

Stop ContentTemplate Selector invoke on TabControl selection change event

I have a TabControl. Each tab Item i.e newly added Tab is rendered using content template selector. But each time i switch between the tabs, content template selector is getting called.
I wanted to stop this. This is because, In the in Tabcontrol, User have provided actions to change the layout, since in the selection change event of the tab Item content template is getting called, it is getting difficult for me to retain the layout what user has changed during Tab Item Selection change Event.
Following is code i am using for the TabControl
<TabControl Grid.Column="2" x:Name="MainTabControl" HorizontalAlignment="Stretch" Margin="0,12,0,7"
SelectedItem="{Binding SelectedTabItem}"
Visibility="{Binding TabsVisible}"
ItemsSource="{Binding ToolsList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ItemTemplate="{StaticResource ToolDisplayDataTemplate}"
commands:FrameworkUICommandList.TabItemChangedCommand="{Binding Path=TabItemChangedCommand}"
>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" ContentTemplateSelector="{DynamicResource toolTabDataItemTemplateSelector}"/>
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem" BasedOn="{StaticResource TabItemStyle}">
<Setter Property="AutomationProperties.AutomationId" Value="{Binding ToolID}" />
<Setter Property="ToolTip" Value="{Binding ToolID,Converter={StaticResource ResourceKey=tabItemTooltipConverter}}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
UPDATE : I have created a sample project to explain my problem. Could not share the project anywhere, so updating the code snippet in main question. sorry for that.
In the sample project, user can add TabItem dynamically to TabControl. Eash Tabcontent can show two Grid panel and they are separated by Grid Splitter. Display of second grid is Based on some flag (in this example ShowSecondPanel). If user click on "Show / Hide Second Panel" button, then second panel content will be shown for the current selected tab.
The Problem is, user can re-size the panels using Grid Splitter, but when user navigates to some other tab and comes back to previous one, the grid splitter position changes to original position.
Hope I am clear on describing the problem.
Code for MainWindow.xaml
<Window x:Class="TabControlTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TabControlTestApp"
Title="MainWindow" Height="500" Width="700">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<local:TabContentTemplateSelector x:Key="tabContentTemplateSelector" />
<DataTemplate x:Key="TabitemDataTemplate">
<StackPanel Width="50" Height="50">
<TextBlock Text="{Binding TabFirstPanel.Name}"></TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="TabContentWithFirstPanel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0">Name :</TextBlock>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TabFirstPanel.Name}"></TextBlock>
</Grid>
</DataTemplate>
<DataTemplate x:Key="TabContentWithBothPanel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"></ColumnDefinition>
<ColumnDefinition Width="5"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0">Name :</TextBlock>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TabFirstPanel.Name}"></TextBlock>
</Grid>
<GridSplitter Grid.Row="0" Grid.Column="1"
VerticalAlignment="Stretch"
Width="5"
Height="Auto"
ResizeDirection="Columns"
ResizeBehavior="PreviousAndNext"
></GridSplitter>
<Grid Grid.Row="0" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0">Additional Detail :</TextBlock>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TabSecondPanel.AdditionalDetails}"></TextBlock>
</Grid>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="1" Grid.Column="0">
<Button Content="Load Tab : 1" Name="LoadTab1" Width="100" Height="50" Command="{Binding LoadTabCommand}" CommandParameter="{Binding ElementName=LoadTab1}"></Button>
<Button Content="Load Tab : 2" Name="LoadTab2" Width="100" Height="50" Command="{Binding LoadTabCommand}" CommandParameter="{Binding ElementName=LoadTab2}"></Button>
<Button Content="Load Tab : 3" Name="LoadTab3" Width="100" Height="50" Command="{Binding LoadTabCommand}" CommandParameter="{Binding ElementName=LoadTab3}"></Button>
</StackPanel>
<Button Content="Close All tab" Width="100" Height="40" x:Name="CloseAllTab"></Button>
<Button Content="Show / Hide Second Panel" x:Name="ShowHideSecondPanelInTab"
Grid.Row="0" Grid.Column="1" Width="150"
Command="{Binding LoadTabCommand}" CommandParameter="{Binding ElementName=ShowHideSecondPanelInTab}"></Button>
<TabControl Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding TabContentList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedTabItem}"
ItemTemplate="{StaticResource ResourceKey=TabitemDataTemplate}"
ContentTemplateSelector="{StaticResource ResourceKey=tabContentTemplateSelector}"
>
</TabControl>
</Grid>
</Window>
Code For MainWindowViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace TabControlTestApp
{
class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
this.loadTabCommand = new LoadTabCommand(this);
tabContentList = new ObservableCollection<TabContent>();
}
public ICommand LoadTabCommand
{
get
{
return this.loadTabCommand;
}
set
{
this.loadTabCommand = value;
}
}
public ObservableCollection<TabContent> TabContentList
{
get
{
return tabContentList;
}
set
{
tabContentList = value;
OnPropertyChanged("TabContentList");
}
}
public TabContent SelectedTabItem
{
get
{
return selectedTabItem;
}
set
{
selectedTabItem = value;
OnPropertyChanged("SelectedTabItem");
}
}
public void LoadFirstTab()
{
TabFirstPanel firstPanel = new TabFirstPanel() { Name = "John-1", Age = "31" };
TabSecondPanel secondPanel = new TabSecondPanel() { AdditionalDetails="Some Details for First Tab" };
TabContent firstTabContent = new TabContent() { TabFirstPanel = firstPanel, TabSecondPanel = secondPanel, ShowSecondPanel = false };
tabContentList.Add(firstTabContent);
SelectedTabItem = firstTabContent;
}
public void LoadSecondTab()
{
TabFirstPanel firstPanel = new TabFirstPanel() { Name = "John-2", Age = "31" };
TabSecondPanel secondPanel = new TabSecondPanel() { AdditionalDetails = "Some Details for second Tab" };
TabContent secondTabContent= new TabContent() { TabFirstPanel = firstPanel, TabSecondPanel = secondPanel, ShowSecondPanel=false };
tabContentList.Add(secondTabContent);
SelectedTabItem = secondTabContent;
}
public void LoadThirdTab()
{
TabFirstPanel firstPanel = new TabFirstPanel() { Name = "John-3", Age = "31" };
TabSecondPanel secondPanel = new TabSecondPanel() { AdditionalDetails = "Some Details for Third Tab" };
TabContent ThirdTabContent = new TabContent() { TabFirstPanel = firstPanel, TabSecondPanel = secondPanel, ShowSecondPanel = false };
tabContentList.Add(ThirdTabContent);
SelectedTabItem = ThirdTabContent;
}
public void ShowHideSecondPanelInTab()
{
TabContent currentTabContent = SelectedTabItem;
int currentIndex = tabContentList.IndexOf(SelectedTabItem);
if (currentTabContent.ShowSecondPanel)
{
currentTabContent.ShowSecondPanel = false;
}
else
{
currentTabContent.ShowSecondPanel = true;
}
TabContentList.RemoveAt(currentIndex);
TabContentList.Insert(currentIndex, currentTabContent);
OnPropertyChanged("TabContentList");
SelectedTabItem = currentTabContent;
}
private TabContent selectedTabItem = null;
private ObservableCollection<TabContent> tabContentList = null;
private ICommand loadTabCommand = null;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Code for TabContentTemplateSelector.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
namespace TabControlTestApp
{
class TabContentTemplateSelector : DataTemplateSelector
{
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null)
{
TabContent tabContent = (TabContent)item;
if (tabContent.ShowSecondPanel)
{
return element.FindResource("TabContentWithBothPanel") as DataTemplate;
}
else
{
return element.FindResource("TabContentWithFirstPanel") as DataTemplate;
}
}
else
{
return base.SelectTemplate(item, container);
}
}
}
}
Code for TabContent Data Object
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TabControlTestApp
{
class TabFirstPanel
{
public string Name { get; set; }
public string Age { get; set; }
}
class TabSecondPanel
{
public string AdditionalDetails { get; set; }
}
class TabContent
{
public TabFirstPanel TabFirstPanel { get; set; }
public TabSecondPanel TabSecondPanel { get; set; }
public bool ShowSecondPanel { get; set; }
}
}
Code for LoadTabCommand Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
namespace TabControlTestApp
{
class LoadTabCommand : ICommand
{
MainWindowViewModel mainWindowViewModel = null;
public LoadTabCommand(MainWindowViewModel mainWindowViewModel)
{
this.mainWindowViewModel = mainWindowViewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
System.Windows.Controls.Button btn = (System.Windows.Controls.Button)parameter;
switch (btn.Name)
{
case "LoadTab1":
mainWindowViewModel.LoadFirstTab();
break;
case "LoadTab2":
mainWindowViewModel.LoadSecondTab();
break;
case "LoadTab3":
mainWindowViewModel.LoadThirdTab();
break;
case "ShowHideSecondPanelInTab":
mainWindowViewModel.ShowHideSecondPanelInTab();
break;
}
}
}
}
I took a look at your example. Thanks for posting code. Now I understand your issue completely. Before I suggested to change DynamicResource to StaticResource I thought you wanted to only look up once for your DataTemplate.
Now I see you wish to keep the instance of DataTemplate alive so TabControl doesn't destroy it when changing tabs.
Here is the solution:
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<local:TabContentTemplateSelector x:Key="tabContentTemplateSelector" />
<DataTemplate x:Key="TabitemDataTemplate">
<StackPanel Width="50" Height="50">
<TextBlock Text="{Binding TabFirstPanel.Name}"></TextBlock>
</StackPanel>
</DataTemplate>
<Grid x:Key="template1">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0">Name :</TextBlock>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TabFirstPanel.Name}"></TextBlock>
</Grid>
<Grid x:Key="template2">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"></ColumnDefinition>
<ColumnDefinition Width="5"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0">Name :</TextBlock>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TabFirstPanel.Name}"></TextBlock>
</Grid>
<GridSplitter Grid.Row="0" Grid.Column="1"
VerticalAlignment="Stretch"
Width="5"
Height="Auto"
ResizeDirection="Columns"
ResizeBehavior="PreviousAndNext"/>
<Grid Grid.Row="0" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0">Additional Detail :</TextBlock>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TabSecondPanel.AdditionalDetails}"></TextBlock>
</Grid>
</Grid>
<DataTemplate x:Key="TabContentWithFirstPanel">
<ContentPresenter Content="{StaticResource template1}"/>
</DataTemplate>
<DataTemplate x:Key="TabContentWithBothPanel">
<ContentPresenter Content="{StaticResource template2}"/>
</DataTemplate>
If you just copy past that you will do fine.
By the way, just as sidenote for you, destroying and building up DataTemplates is very important in wpf for releasing unmanaged memory.
I don't believe you may avoid this behavior, unless you use another control other than the TabControl.
The TabControl is a ItemControl-derived component: if its content is created via DataTemplate (as you done), the actual content is generated every time upon the current selection.
Another option is to fill the TabControl with direct content, and the inner controls should be preserved across the selection.
Have a look here:
http://msdn.microsoft.com/en-us/library/system.windows.controls.tabcontrol(v=vs.110).aspx
UPDATE: First off, I am not sure to have understand what you want, but here is a solution based on what I mean.
The fundamental trick is hosting directly the tab-contents, instead of creating via data-templating. That is:
<TabControl>
<TabItem Header="Tab1">
<TextBlock
Text="Page 1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
/>
</TabItem>
<TabItem Header="Tab2">
<CheckBox
Content="check me"
HorizontalAlignment="Center"
VerticalAlignment="Center"
/>
</TabItem>
<TabItem Header="Tab3">
<TextBlock
Text="Page 3"
HorizontalAlignment="Center"
VerticalAlignment="Center"
/>
</TabItem>
</TabControl>
The above snippet should "persist" the checkbox value across the tabs flipping.
But you need (or desire) some kind of templating, or a dynamic way to create the right content upon a certain data added to the tabcontrol.
The following trick should solve your problem:
<TabControl>
<TabItem
Header="Tab1"
>
<ContentControl
Content="{Binding Path=A}"
ContentTemplateSelector="{StaticResource sel}"
/>
</TabItem>
<TabItem
Header="Tab2"
>
<ContentControl
Content="{Binding Path=B}"
ContentTemplateSelector="{StaticResource sel}"
/>
</TabItem>
<TabItem
Header="Tab3"
>
<ContentControl
Content="{Binding Path=C}"
ContentTemplateSelector="{StaticResource sel}"
/>
</TabItem>
</TabControl>
This is using a trivial set of templates as follows:
<Window.Resources>
<local:MySelector x:Key="sel" />
<DataTemplate x:Key="dtplA">
<StackPanel Margin="30,30">
<TextBlock Text="A: " />
<TextBox />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="dtplB">
<StackPanel Margin="30,30">
<TextBlock Text="B: " />
<TextBox />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="dtplC">
<StackPanel Margin="30,30">
<TextBlock Text="C: " />
<TextBox />
</StackPanel>
</DataTemplate>
</Window.Resources>
And the behind-code is even more trivial:
public class VM
{
public A A { get; set; }
public B B { get; set; }
public C C { get; set; }
}
public class A { }
public class B { }
public class C { }
public class MySelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item != null)
{
return (DataTemplate)((FrameworkElement)container).FindResource("dtpl" + item.GetType().Name);
}
else
{
return base.SelectTemplate(item, container);
}
}
}
If you try this small example, the text typed into the textboxes will persist across the tabs flipping. That is, the templates will be called once only.
Let me know.

Categories