WPF UserControl loading dynamically based on which button is clicked - c#

I am very new to WPF and this is my 1st application.
I have a Ribbon with different buttons. I want to load a UserControl based on which button is clicked.
I have a button called "Change Password" and I created the UserControl that will represent the UI to change the password.
I have another button called "Unlock Account" and I have a UserControl that can unlock an account.
App --> Ribbon --> RibbonButton --> SwappableUserControlAtRunTime
I want to use the same space in my Window to load/unload UserControls based on whats clicked.
I am using WPF 4.5 and .Net 4.5 on Windows 8.1
I am targeting PC's with .Net 4.5 and Windows 7+

You can load the UserControls in the ContentControl. Refer below code.
<RibbonWindow x:Class="LayoutWPF_Learning.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Ribbon Grid.Row="0">
<RibbonTab Header="Load UC">
<StackPanel Orientation="Horizontal">
<Button Content="UserControl1" Click="Button_Click"/>
<Button Content="UserControl2" Click="Button_Click_1"/>
</StackPanel>
</RibbonTab>
</Ribbon>
<ContentControl Grid.Row="1" x:Name="cntCtrl" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
</Grid>
public partial class MainWindow : RibbonWindow
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
cntCtrl.Content = new UserControl1();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
cntCtrl.Content = new UserControl2();
}
}

Even though you're going with codebehind (I'd recommend you look into MVVM) you'll be able to use a Converter to do this, this is more the WPF way of working than altering controls at runtime from your C# code.
There'll be loads of examples of how to use converters; Google "WPF Converter". Here's one that's relevant to your requirement: http://www.codeproject.com/Tips/285358/All-purpose-Boolean-to-Visibility-Converter

Related

C# WPF How to navigate to an existing page from the MainWindow?

I have a task which has to implement the MVVM pattern.
I have a MainWindow with a button, successfully tied it up to a working and tested command.
My goal is to navigate to an existing Page on button click, but the problem is:
-NullReferenceException()
Corresponding MainWindow.xaml part
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Frame x:Name="MainFrame" NavigationUIVisibility="Hidden" Content="{Binding MainFrame}" ></Frame>
ETC
Corresponding ViewModels clickCommand:
private void ExecuteMethod(object parameter)
{
View.Home homePage = new View.Home();
mainFrame.Content = homePage;
MainFrame.Navigate(homePage);
}
Page to be loaded is an existing page with several controls in it, yet I fail to even navigate to there, since
mainFrame.Content = homePage;
throws a NullReferenceException(object reference not set to an instance of an object)
What am I missing?

Removing the reference to a user control in wpf

I have a wpf application which has a main window and menu. This main window has a panel, and on clicking the menu item i create an instance of the user control and load the panel with the control.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="" MinHeight="750" Height="Auto" MinWidth="1100" Width="Auto" WindowState="Maximized" ScrollViewer.VerticalScrollBarVisibility="Auto"
Loaded ="MainWindow_OnLoaded" Closing="Window_Closing">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility ="Auto" SizeChanged="ScrollViewer_SizeChanged">
<Grid Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="38"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Height="38" Width="Auto" Background="#09527B">
<Grid Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="70"></ColumnDefinition>
</Grid.ColumnDefinitions>
</Grid>
</StackPanel>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="189"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<StackPanel>
<Expander Name="test" Header="Admin" Foreground="White" Margin="0,10,0,0">
<StackPanel Margin="20,0,0,0">
<Expander Header="Data" Foreground="White">
<StackPanel>
<TextBlock Text="Add/Edit UC1" Foreground="White" Margin="30,5,0,0" MouseDown="OpenUC1_MouseDown" MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave"/>
<TextBlock Text="Add/Edit UC2" Name="tbxBuild" Foreground="White" Margin="30,5,0,0" MouseDown="OpenUC2_MouseDown" MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave"/>
</StackPanel>
</Expander>
</StackPanel>
</Grid>
<StackPanel Grid.Column="1">
<Grid Name="pnlMain" Height ="Auto" VerticalAlignment="Top" HorizontalAlignment="Left">
</Grid>
</StackPanel>
</Grid>
</Grid>
</ScrollViewer>
</Window>
MainWindow.cs
private void OpenUC1_MouseDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < pnlMain.Children.Count; i++ )
{
pnlMain.Children.Remove(pnlMain.Children[i]);
}
using (UC2 _uc2= new UC2())
{
pnlMain.Children.Add(_uc2);
}
}
private void OpenUC2_MouseDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < pnlMain.Children.Count; i++ )
{
pnlMain.Children.Remove(pnlMain.Children[i]);
}
using (UC1 _uc1= new UC1())
{
pnlMain.Children.Add(_uc1);
}
}
My question is when I remove the control(UC1) from the main panel, when will that control be disposed?
Both the user control(UC1 and UC2) has the same view model attached to its data context. So i find that some of the methods in the removed user control(UC1) is called even though that is removed from the panel. The reason being, when a new instance of UC2 is created, there are some changes in the data model which in effect calls the dependent methods in UC1.
But if UC1 had been disposed this wouldn't happen. How can I make sure UC1 is disposed before instance of UC2 is created?
public UC1()
{
InitializeComponent();
this.DataContext = App.ViewModel.TestViewModel;
}
private void UC1_Unloaded(object sender, RoutedEventArgs e)
{
this.DataContext = null;
}
public UC2()
{
InitializeComponent();
this.DataContext = App.ViewModel.TestViewModel;
}
private void UC2_Unloaded(object sender, RoutedEventArgs e)
{
this.DataContext = null;
}
The unloaded method is not called immediately when the control is removed from the panel.
When I write and test code to dynamically add and remove a UserControl object from a window's visual tree, I find that the Unloaded event is raised just as expected.
In your own code example, there is at least one serious problem, and two incongruities:
The serious problem is how you are removing children. Your for loop is iterating by index through the children of the pnlMain object (a Grid). But removing any child invalidates the sequence of indexes! That is, the loop will first remove the child at index 0; this causes the child at index 1 to now become the child at index 0. But the loop increments the index before continuing, and will next remove the child at index 1. This child was originally at index 2. The code skips every other child (i.e. the ones originally at odd-numbered indexes), leaving half of them attached as children of the Grid.
Incongruity #1: I would expect a method with the phrase "OpenUC1" in the name to add an instance of UC1. However, your OpenUC1_MouseDown() method seems to be adding an instance of UC2 (and vice a versa for OpenUC2_MouseDown()). At the very least, there should be a comment in the code explaining why the code is different from what one might expect given the name of the method.
Incongruity #2: there is a using statement around the call to Add() when adding the UserControl objects. First, UserControl itself does not implement IDisposable, so unless your types have implemented that interface, that code is not even legal. Second, even if your UserControl subclasses do implement that interface, it does not seem like a very good idea to me to dispose an object that you've just created and which you are retaining in the visual tree (i.e. by adding it to the Grid's children).
Unfortunately, as I mentioned in my comment, without a good, minimal, complete code example that reliably reproduces your problem, it is impossible to say why your code does not behave as one would hope and/or expect it to. It is possible that any of the above points (but especially #1) are the cause of the behavior you're seeing, but I have no way to know for sure.
If after addressing those issues (or determining somehow that they are not problems…though if you can legitimately do that, I would argue that the code is still defective, in the sense that it's poor design), you find that your problem still exists, please edit your question so that it includes a good, minimal, complete code example that reliably reproduces the problem.
In the meantime, here is a simple code example that illustrates the basic behavior of the Unloaded event being raised just as expected when the object is removed from the visual tree. Note that while the correct way to remove all children from the Grid object's Children collection is to simply call the Clear() method (e.g. pnlMain.Children.Clear()), I have included an example of a explicit loop-based approach that does work.
XAML:
UserControl1.xaml
<UserControl x:Class="TestSO33289488UserControlUnloaded.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Unloaded="UserControl_Unloaded"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="UserControl" FontSize="36"/>
</Grid>
</UserControl>
MainWindow.xaml
<Window x:Class="TestSO33289488UserControlUnloaded.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button x:Name="button1" Content="Add UserControl"
HorizontalAlignment="Left" Click="Button_Click"/>
<Grid x:Name="grid1"/>
</StackPanel>
</Window>
C#:
UserControl1.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace TestSO33289488UserControlUnloaded
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
MessageBox.Show("UserControl.Unloaded was raised");
}
}
}
MainWindow.xaml.cs
using System.Windows;
namespace TestSO33289488UserControlUnloaded
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private bool _removeUserControl;
private void Button_Click(object sender, RoutedEventArgs e)
{
if (_removeUserControl)
{
//grid1.Children.Clear();
// Calling Clear() is better, but if you really want to loop,
// it is possible to do correctly. For example:
while (grid1.Children.Count > 0)
{
grid1.Children.RemoveAt(grid1.Children.Count - 1);
}
button1.Content = "Add UserControl";
}
else
{
grid1.Children.Add(new UserControl1());
button1.Content = "Remove UserControl";
}
_removeUserControl = !_removeUserControl;
}
}
}
Quote from an MSDN forum entry about Loaded/Unloaded events:
The events are raised asynchronously, so there might be some delay
between the action that causes the event and the event itself. The
events are effectively put into a list and a task is added to the
dispatcher's queue. When that task runs, it raises the events on the
list.
So the answer is you can't predict when exactly these events will raised and you shouldn't expect that they will be called immediately after you removed a control from it's parent.
It's kinda difficult to give you a proper solution without seeing the full project, but here's a quick and dirty solution: rather than making sure that the given user controls' events are fired in time let's check the Parent property of the UC1/UC2 object before running the method. If the property is null then the UC1/UC2 object was removed and you should not execute that method.
But let me point out some problems with this code:
What's the point of the using block in the MouseDown event handlers? You create a user control object, add it to the panel and then immediately after that you call the Dispose method on it? (that's what the using block does in C#)
You don't need a for loop to remove all the children elements from a Panel control like a Grid. You can do that in one line. pnlMain.Children.Clear();

Insert XAML into WPF custom control in the editor

I have a XAML main window that contains a header, a central area and a footer (in a grid). The central area contains a ContentControl which is set throw a binding (using MVVMLight). The header/footer is always the same so no problems there.
The part that goes into the ContentControl is always quite similar, they are WPF usercontrols and have a left part that contains info and a right part with at least an OK and BACK button.
These are viewmodels and their views:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<TextBlock Text="this changes and contains other controls too" />
</Grid>
<Grid Grid.Column="1">
<!-- more buttons and statuses-->
<Button Content="Back" Margin="5" Height="30" />
<Button Content="Ok" Margin="5" Height="30" />
</Grid>
</Grid>
Is there a way i could create a base class/custom control for those views? So that I could write something like this in my xaml:
<basewindow>
<leftpart>
custom XAML for this view
</leftpart>
<rightpart>
custom XAML for this view
</rightpart>
</basewindow>
I could then remove duplicate code that is now in each of those views to the base class while still keeping the ability to write my xaml in the editor. Or is this not feasible?
To clarify are you trying to inherit the visual element that exist in XAML, like you can do in WinForms? If so you cannot do this in WPF. There is no Visual inheritence in WPF.
Now if you aren't trying to inherit visual element it is easy. First create your UserControlBase class and add you event handler. Keep in mind this base class can not have any XAML associated with it. Code only
public class MyUserControlBase : UserControl
{
public MyUserControlBase()
{
}
protected virtual void Button_Click(object sender, RoutedEventArgs e)
{
}
}
Now create another UserControl that does have a xaml counter part. Now you will need to change the root elemtn in the XAML to your base class like this:
<local:MyUserControlBase x:Class="WpfApplication7.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication7">
<Grid>
<Button Click="Button_Click">My Button</Button>
</Grid>
</local:MyUserControlBase>
And don't forget the code behind:
public partial class MyUserControl : MyUserControlBase
{
public MyUserControl()
{
InitializeComponent();
}
}
Notice the button in the derived user control is calling the Button_Click event handler we defined in the base class. That is all you need to do.

How to use WinForms host in XAML file OR put it in Toolbox in WPF project

I've read tutorial how to embed Form in WPF's Window http://tech.pro/tutorial/786/wpf-tutorial-using-winforms-in-wpf I test it and it seems to be easy. But the problem is that I have create WinForm host programmatically in the .cs file. This is inconvienient to me.
I would like to put it in the XAML to have better control over its size. If it is possible I also would like to add WinForms host to toolbox
I am not sure why you would want to do that.. but anyhow because the Winform is a top-level control.
public partial class MainWindow : Window
{
Form1 frm = null;
public MainWindow()
{
InitializeComponent();
frm = new Form1();
frm.TopLevel = false;
}
private void frmMain_Loaded(object sender, RoutedEventArgs e)
{
myHost.Child = frm;
}
}
Happy Coding..
This is the accompany XAML...
<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" WindowState="Maximized">
<Grid x:Name="frmMain" Background="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}" Loaded="frmMain_Loaded">
<DockPanel LastChildFill="True">
<WindowsFormsHost x:Name="myHost" HorizontalAlignment="Stretch" Height="Auto" Margin="0" VerticalAlignment="Stretch" Width="Auto"/>
</DockPanel>
</Grid>
</Window>
Try it out... I assure you it works and..

How do i navigate from one Xaml file to another?

I am very new to XAML and WPF.I have a problem.
I have two files. first.xaml and second.Xaml. There is a button in first.xaml, on click of which it should navigate to second.xaml.How do i achieve this.
This is one way of organising the code:
Your second.xaml should contain your window definiton e.g.:
<Window x:Class="MediaCheckerWPF.AboutBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="About Media Checker" Height="300" Width="400" ResizeMode="NoResize"
Icon="/MediaCheckerWPF;component/Resources/checker.ico"
ShowInTaskbar="False">
<Grid>
...
</Grid>
</Window>
Your first.xaml has the button e.g.:
<Button Height="23" HorizontalAlignment="Right" Name="aboutButton"
VerticalAlignment="Top" Width="23" Click="AboutButton_Click"
Content="{DynamicResource TInformationButton}"
ToolTip="{DynamicResource TInformationButtonTooltip}" Margin="0,0,8,0"/>
Then in the code behind:
private void AboutButton_Click(object sender, RoutedEventArgs e)
{
var about = new AboutBox { Owner = this };
about.Initialise();
about.Show();
}
ChrisF's answer is a good way of popping up a new window in a Windows-style application, except you should not call Initialize that way (it should be called from the constructor).
If you want web-style navigation instead, you should use the Page class instead of Window, along with NavigationService. This also allows your WPF application to run inside an actual web browser.

Categories