I have a MainWindow and 4 UserControls. By switching the DataContext to my UserControls I can have an application with multiple "Pages". In every UserControl I have a webBrowser-Control that Displays an PowerPoint (so -> 4 UC = 4 ppt). The issue I have now is that when I Switch my DataContext (Switch Page) I have to load (call the navigate Method) the whole ppt in my webBrowser again and that takes some Time. How can I fix this?
thanks in advance :))
Adrian
EDIT CODE
MainWindow.xaml
<Window.Resources>
<DataTemplate x:Name="Page1Template" DataType="{x:Type viewmodels:Page1Model}" >
<views:Page1 DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="Page2Template" DataType="{x:Type viewmodels:Page2Model}">
<views:Page2 DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="Page3Template" DataType="{x:Type viewmodels:Page3Model}">
<views:Page3 DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="Page4Template" DataType="{x:Type viewmodels:Page4Model}">
<views:Page4 DataContext="{Binding}"/>
</Window.Resources>
// ...
<ContentControl Content="{Binding}"></ContentControl>
MainWindow.xaml.cs (i call the page Switch like this)
private void menuBtn1_Click(object sender, RoutedEventArgs e)
{
DataContext = new Page1Model();
}
private void menuBtn2_Click(object sender, RoutedEventArgs e)
{
DataContext = new Page2Model();
}
private void menuBtn3_Click(object sender, RoutedEventArgs e)
{
DataContext = new Page3Model();
}
private void menuBtn4_Click(object sender, RoutedEventArgs e)
{
DataContext = new Page4Model();
}
and lets say e.g my UserControl1: (when i call UC1 every time the ppt is opening again, i want just to open it one time):
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
powerPointBrowser1.Navigate("somePPTfile.pptx");
powerPointBrowser1.LoadCompleted += powerPointBrowser1_LoadCompleted;
}
i hope i made it clear :S
In Mainwindow.xaml Place the view in stackpanel to make visiblity hide show like this.
<DataTemplate x:Name="Page1Template" DataType="{x:Type viewmodels:Page1Model}">
<StackPanel Visibility="{Binding Page1}">
<views:Page1 DataContext="{Binding}"/>
</StackPanel>
</DataTemplate>
In MainWindow.xaml.cs you have to set a property for visibility like this.
private Visibility page1;
public Visibility Page1
{
get { return page1; }
set { page1 = value; }
}
Then initialize the DataContext of each view in your MainWindowLoad function, so that it will be preloaded. After that you can set the visibility for the each view in your each menu click function like this:
Page1 = Visibility.Visible; or Page1 = Visibility.Collapsed;
I hope it will works.
Related
hi I'm try to develop Datalogger, so i create a menu, to Switch de Options i decided to use DataTemplates and different ViewModels.
Menu
XAML:
<Window.Resources>
<DataTemplate x:Name="GraficoVtemplate" DataType="{x:Type viewmodels:GraficoVM}">
<view:GraficoV DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Name="ListaVtemplate" DataType="{x:Type viewmodels:ListaVM}">
<view:ListaV DataContext="{Binding }"/>
</DataTemplate>
<ContentControl Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="4" Grid.RowSpan="5" Content="{Binding}"/>
This is how i change the datacontext;
XAML.CS
private void Novoteste_btn_Click(object sender, RoutedEventArgs e)
{
DataContext = new NovoTesteVM();
}
private void Lista_btn_Click(object sender, RoutedEventArgs e)
{
DataContext = new ListaVM();
}
This is the files that i have, the models are empty and the viewsmodals have some controles.Files
The problem is that when I change the menu, the content of the previous menu is not saved, that is when I select the menu "lista" and fill in a datagrid, and I go to another menu when I select again the menu "lista" the data are lost.I do not know what I need to add, or change so that the data is not lost
Thanks you for the explanation!
Edit 1:
MainWindows.xaml.cs
private void Novoteste_btn_Click(object sender, RoutedEventArgs e)
{
DataContext = NovoTesteVM.NovoTesteViewModel;
}
private void Grafico_btn_Click(object sender, RoutedEventArgs e)
{
DataContext = GraficoVM.Grafico;
}
NovoTesteVM.cs
public class NovoTesteVM
{
private static NovoTesteVM novoTesteViewModel;
public static NovoTesteVM NovoTesteViewModel
{
get
{
novoTesteViewModel = novoTesteViewModel ?? new NovoTesteVM();
return novoTesteViewModel;
}
}
}
Create container properties to keep the wiewmodel for corresponding views. It's loosing data because you are initializing new object on click.
Sample code -
private NovoTesteVM novoTesteViewModel;
public NovoTesteVM NovoTesteViewModel
{
get
{
novoTesteViewModel = novoTesteViewModel ?? new NovoTesteViewModel();
return novoTesteViewModel;
}
}
I have created custome window (titlebar, min/max/ext buttons, own border for window manipulation and lots of styles and triggers).
There are 5 methods defined (which i would like to override):
From window markup:
SourceInitialized="Window_SourceInitialized"
Closing="Window_Closing"
From Titlebar buttons:
Exit_Click()
Max_Click()
Min_Click()
And at last I have DockPanel
<DockPanel Name="ClientArea"/>
In which I want to put my content
I have tried to add content from code:
BaseWindow editInterfaceWindow = new BaseWindow() { Owner = this };
editInterfaceWindow.DataContext = new EditInterface();
editInterfaceWindow.ShowDialog();
But this way some bindings stoped working and inside editInterfaceWindow I cant create another window this way because of Owner = this. There are also some problems with InitializeComponent() in constructor.
And ListView inside EditInterface UserControl <ListView Name="LBAvaliable" ItemsSource="{Binding AvaliableFaces, UpdateSourceTrigger=PropertyChanged}"> is not visible in code as LBAvaliable.
I have used that window few times, filling ClientArea with content by hand.
How should I create other windows, so that I can just inherit it or just define binding? So my XAML for every single window does not take ~1000 lines of code.
In the past I've used MVVMCross Framework and we never had to worry about this ourselves. Though this is not the best, here's an idea on what you can do.
Create a view model that can be overridden for your user control.
Set data templates.
Programmatically change the view model for your user control's main content and let data templates do the work for the UI.
View Model: Pre-defined 3 button actions ready for you to set/override.
public class MainUCViewModel : ViewModelBase
{
private Action<object> btnACommand;
private Action<object> btnBCommand;
private Action<object> btnCCommand;
private object ccVM;
public ViewModelBase CCVM
{
get { return this.ccVM; }
set
{
this.ccVM = value;
OnPropertyChanged(); // Notify View
}
}
public MainUCViewModel()
{
}
public RelayCommand BtnACommand
{
get { return new RelayCommand(btnACommand); }
}
public RelayCommand BtnBCommand
{
get { return new RelayCommand(btnBCommand); }
}
public RelayCommand BtnCCommand
{
get { return new RelayCommand(btnCCommand); }
}
public void SetBtnACommand(Action<object> action)
{
this.btnACommand = action;
}
public void SetBtnBCommand(Action<object> action)
{
this.btnBCommand = action;
}
public void SetBtnCCommand(Action<object> action)
{
this.btnCCommand = action;
}
}
View:
<UserControl x:Class="WpfApplication1.Views.UserControls.MainUC"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="750">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="45" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding BtnACommand}" Width="100">
<TextBlock>A</TextBlock>
</Button>
<Rectangle Width="15" />
<Button Command="{Binding BtnBCommand}" Width="100">
<TextBlock>B</TextBlock>
</Button>
<Rectangle Width="15" />
<Button Command="{Binding BtnCCommand}" Width="100">
<TextBlock>C</TextBlock>
</Button>
</StackPanel>
</Grid>
<Grid Grid.Row="1">
<ContentControl x:Name="CCMain" Content="{Binding CCVM}"/>
</Grid>
</Grid>
</UserControl>
Look at Thinking with MVVM: Data Templates + ContentControl. Simply define the data template for your view model.
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:GeneralSettingsViewModel}">
<View:GeneralSettingsView/>
</DataTemplate
<DataTemplate DataType="{x:Type ViewModel:AdvancedSettingsViewModel}">
<View:AdvancedSettingsView/>
</DataTemplate>
</Window.Resources>
What I’m saying here is that GeneralSettingsViewModel should be
rendered using a GeneralSettingsView. That’s exactly what we need !
Because the Views are created using a DataTemplate, we do not need to
setup the DataContext, it will be automatically registered to the
templated object, the ViewModel.
There are two main approaches to your problem:
Inherited windows
Configurable windows
For approach 1, design your window and make the methods overrideable:
In base window xaml, assign the handlers and everything you want:
<Window x:Class="WpfTests.MainWindow"
...
SourceInitialized="Window_SourceInitialized">
In base window, define the handlers as protected virtual (or abstract, if you like to enforce their implementation)
public partial class MainWindow : Window
{
// ...
protected virtual void Window_SourceInitialized(object sender, EventArgs e)
{
}
// ...
}
Create derived windows
public class ExWindow : MainWindow
{
protected override void Window_SourceInitialized(object sender, EventArgs e)
{
// specialized code here
}
}
Change App.xaml to use Startup instead of StartupUri
<Application x:Class="WpfTests.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="Application_Startup">
And manually create your first window, chosing one of the inherited window classes
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
var window = new ExWindow();
window.Show();
}
}
The second approach - configurable windows - follows the same principle as a good user control design: The window/control properties are controlled by the creator instead of being controlled by the window/control itself.
So, instead of defining some event handler within the window code, just leave this exercise to the user, who hopefully knows what the window should do:
public partial class MainWindow : Window
{
// I don't care for SourceInitialized (also remove it from XAML)
}
In App.xaml or wherever a window is created:
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
var window = new MainWindow();
window.SourceInitialized += window_SourceInitialized;
window.Show();
}
void window_SourceInitialized(object sender, EventArgs e)
{
var window = sender as MainWindow;
// I know how to handle this event for this window instance
}
}
Below is an mcve (to reproduce the problem it has to be datatemplated unloadable view).
xaml:
<Window Content="{Binding}" ... >
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel}">
<ListView ItemsSource="{Binding Items}"
SelectionChanged="ListView_SelectionChanged"
Unloaded="ListView_Unloaded"
MouseRightButtonDown="ListView_MouseRightButtonDown">
</ListView>
</DataTemplate>
</Window.Resources>
</Window>
cs:
public class ViewModel
{
public ObservableCollection<string> Items { get; set; } = new ObservableCollection<string>(new[] { "1", "2", "3" });
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e) => Title += "S";
void ListView_Unloaded(object sender, RoutedEventArgs e) => Title += "U";
void ListView_MouseRightButtonDown(object sender, MouseButtonEventArgs e) => DataContext = null;
}
Start program, select item in the list ("S" will be added to title), right click inside list -> "SU" will be added to title.
Question: why SelectionChanged is called when view (ListView) is unloaded??
Right-clicking without selecting anything will add only "U" to the tittle.
Simply closing software (disregarding selection) will not cause SelectionChanged (tested by setting breakpoint).
I will have some logic in SelectionChanged event and I don't want it to run when view is unloaded, it's unexpected behavior what SelectionChanged event is called in this case at all.
I have a checkbox in my datatemplate and for some reason the events are not firing. see code below. my datatemplate is in a resource dictionary with code behind file. Any Ideas?
<ResourceDictionary x:Class="ArmyBuilder.Templates.EquipmentDataTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<DataTemplate x:Key="EquipmentDataTemplate">
<Grid>
<CheckBox Content="{Binding Name}" Checked="ToggleButton_OnChecked" Click="CheckBox_Click"/>
</Grid>
</DataTemplate>
//code behind
namespace ArmyBuilder.Templates
{
public partial class EquipmentDataTemplate : ResourceDictionary
{
public EquipmentDataTemplate()
{
InitializeComponent();
}
private void ToggleButton_OnChecked(object sender, RoutedEventArgs e)
{
// breakpoint not hit
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
// breakpoint not hit
}
}
}
I am not sure how you use it, but the your code works for me and the click event got fired. Check the following and if you still cannot find the point, share a repro project to show how you used it.
Template XAML:
<ResourceDictionary x:Class="App10.EquipmentDataTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<DataTemplate x:Key="EquipmentDataTemplate">
<Grid>
<CheckBox Content="Click Me" Checked="ToggleButton_OnChecked" Click="CheckBox_Click"/>
</Grid>
</DataTemplate>
</ResourceDictionary>
Template cs:
namespace App10
{
public sealed partial class EquipmentDataTemplate : ResourceDictionary
{
public EquipmentDataTemplate()
{
this.InitializeComponent();
}
private void ToggleButton_OnChecked(object sender, RoutedEventArgs e)
{
// breakpoint not hit
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
// breakpoint not hit
}
}
}
In MainPage.Xaml, use the template in a ListView:
<Page
x:Class="App10.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App10"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<local:EquipmentDataTemplate></local:EquipmentDataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="listView" CanReorderItems="True" AllowDrop="True" ItemTemplate="{StaticResource EquipmentDataTemplate}">
</ListView>
</Grid>
</Page>
In MainPage cs:
namespace App10
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
var list = new ObservableCollection<string>();
for (int i = 0; i < 10; i++)
{
list.Add("Item " + i);
}
listView.ItemsSource = list;
}
}
}
I've two windows: Main Window, Log Window. How can I update the listbox in the Log Window when some action is happened in the Main Window (e.g. button is clicked)?
Below is the code for listbox in Log Window:
<ListBox x:Name="DebugLogLb" BorderBrush="{x:Null}">
<TextBlock x:Name="DebugLogTb" Text="{Binding LogText}" Background="{x:Null}" />
</ListBox>
When the button in the Main Window is clicked, it will update the listbox. I tried with the code below but it doesn't work.
private void Btn1_Click(object sender, RoutedEventArgs e)
{
var log = new LogWindow();
log.DebugLogLb.Items.Add(new { LogText = "Button 1 is clicked" });
}
I'm able to update the listbox if I put everything in the same window, but I failed to do so with two windows.
My expected output would be like:
Even if both windows are opened, when the buttons in the Main Window are clicked, it will directly update in the Log Window as well.
Thanks for any helps in advanced.
It's hard to tell where you are going wrong without seeing more of the code. This is an example that works. It creates a new LogWindow in the MainWindow ctor and sets the DataContext. When the button is clicked the handler calls show on the window. The ListBox's itemssource property is bound to an ObservableCollection of strings. So any adds/removes are automatically updated on the UI.
LogWindows xaml
<Window x:Class="WpfApplication7.LogWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LogWindow" Height="300" Width="300">
<Grid>
<ListBox x:Name="DebugLogLb" BorderBrush="{x:Null}" ItemsSource="{Binding LogText}" />
</Grid>
MainWindow code-behind
public partial class MainWindow : Window
{
LogWindow _logWindow;
public MainWindow()
{
InitializeComponent();
LogText = new ObservableCollection<string>();
_logWindow = new LogWindow();
_logWindow.DataContext = this;
_logWindow.Closed += _logWindow_Closed;
}
private void _logWindow_Closed(object sender, EventArgs e)
{
_logWindow = new LogWindow();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_logWindow.Show();
LogText.Add("Button1 Clicked");
}
public ObservableCollection<string> LogText { get; set; }
}