I have a main WPF Window with a Menu Bar and a Status Bar. I want this window to act as MDI Window. I want to define the entire area from below the Menu Bar to just above the Status Bar, where I will be showing other Windows. I want to define a Page or Panel like thing. When I add a new WPF Form, I want it to appear on this location contained within the Main Window. How to do this?
I've used http://wpfmdi.codeplex.com/ in basic prototype apps. Create your content as user controls, which are then wrapped in a window.
I've never used it with the toolbox, though.
Simple version:
<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" Height="350" Width="525">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="File"/>
<MenuItem Header="Edit"/>
<MenuItem Header="Help"/>
</Menu>
<StatusBar DockPanel.Dock="Bottom"/>
<ContentControl Name="_content"/>
</DockPanel>
</Window>
And code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public object MainContentGoesHere
{
get { return _content.Content; }
set { _content.Content = value; }
}
}
Then in your code just have a reference to a MainWindow - and use the MainContentGoesHere property to inject the views. Just remember to use UserControls, Panels or ContentControl instead of Window, when you inject them as Window cannot have a parent.
If you want something more fancy - take a look at Prism on CodePlex. It might be overkill for you, but it is actually a well-thought out and lightweight framework.
EDIT: Fixed the order of Children in the DockPanel.
Related
In a mvvm application some areas inside a window (in reality it is a UserControl inside MainWindow) are dynamically displayed according to the user selections.
The changing blocks are inside Stackpanels, I have 4 of them and only one at a time is displayed. This is accomplished binding Visibility to a bool property and using the BooleanToVisibilityConverter.
I put all the alternate StackPanel inside parent control. It works correctly, but during design phase in Visual Studio I see all of them, so I have problems in figuring the final layout.
How can I easily create the layout having more controls which share the same window area and are displayed one at a time ?
Setting A Design Time Only Data Context
Developing XAML in the Studio Designer can be greatly simplified by setting the Design-Time Data Context.
One implementation is based on setting a duplicate DataContext which will be ignored during the final compilation.
To implement the switching, add to the ViewModel, a property that will inform the designer whether it can be used in Development Mode or not.
I use an MVVMLight situation for this example, but for this declared instance property IsInDesignMode and static property ViewModelBase.IsInDesignModeStatic.
Example:
using System.ComponentModel;
namespace DataContextDesignTime.Example
{
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _flag;
public bool Flag
{
get => _flag;
set
{
if (!Equals(_flag, value))
{
_flag = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Flag)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NotFlag)));
}
}
}
public bool NotFlag => !Flag;
}
}
<Window x:Class="DataContextDesignTime.Example.ExamleWindow"
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:DataContextDesignTime.Example"
mc:Ignorable="d"
Title="ExamleWindow" Height="450" Width="800">
<d:Window.DataContext>
<local:MyViewModel Flag="True" NotFlag="True"/>
</d:Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<StackPanel>
<Border Background="LightBlue" Height="200"
Visibility="{Binding Flag, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Border Background="LightGreen" Height="400"
Visibility="{Binding NotFlag, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
</Window>
In this example, you can change property values in XAML or in the Property Browser.
And you will immediately see the work of your bindings, triggers, how the display for certain data changes.
Note
This may fail on more complex VMs/packages, but in general by setting the DataContext at design time is not difficult.
I need to recompile the project to see the changes in the properties.
The XAML Designer panel has an «Enable/Disable Project Code» button.
, but during design phase in Visual Studio I see all of them, so I have problems in figuring the final layout.
This problem is easily resolved by bringing up the Document Outline tab in visual studio. Once open, navigate to the visible tree and toggle the eyeball to visibly hide/unhide the control[s] one is not interested in; during design time only.
I have a listbox to select an item for edit. I have an edit button as well. Call this the MainView[Model].
If I press the edit button the MainView[Model] shall be replaced by EditView[Model].
The EditView shall not be displayed in a area below or beside the MainView. It should be completely replaced or at least completely hide the MainView.
If edit is finished (OK, cancel) the MainView shall be displayed again.
I have tried to overlay a ContentControl but with no success.
Now, I'm thinking about a kind of NavigatorViewModel which has multiple ViewModels exposed by a property. But I'm not sure if this is the right direction to go.
Can anybody help?
Thx.
You would preferably use the Conductor pattern that Caliburn.Micro provides. A conductor manages one or more Screens and controls their lifetime. See Screens, Conductors and Composition for further information.
At first, we need a shell. This is your "NavigatorViewModel". It is derived from Conductor<Screen>.Collection.OneActive, what means that it holds a list of Screens of which a single one can be active at a time:
public interface IShell
{
void ActivateItem(Screen screen);
}
public class ShellViewModel : Conductor<Screen>.Collection.OneActive, IShell
{
public ShellViewModel()
{
this.ActivateItem(new MainViewModel());
}
}
A conductor has an ActiveItem property, and we want to bind a ContentControl to it, so we see the corresponding view:
<!-- ShellView.xaml -->
<Window x:Class="WpfApplication1.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ContentControl Name="ActiveItem" />
</Window>
Our MainViewModel can navigate to the EditViewModel using its parent, the shell:
public class MainViewModel : Screen
{
public void Edit()
{
((IShell)this.Parent).ActivateItem(new EditViewModel());
}
}
We bind a button to the Edit method:
<!-- MainView.xaml -->
<UserControl x:Class="WpfApplication1.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Button Name="Edit" Content="Edit" />
</UserControl>
EditViewModel also derives from Screen and just contains your edit logic:
public class EditViewModel : Screen
{
}
Finally, we bind a button to the TryClose method, so the view model closes itself and is removed from the shell's items. The last activated item (MainViewModel) will be reactivated:
<!-- EditView.xaml -->
<UserControl x:Class="WpfApplication1.EditView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Button Name="TryClose" Content="Back" />
</UserControl>
That's about it.
We are using some ActiveX or windows form control which don't have the WPF equivalences, so naturally we use WindowsFormsHost to host these controls. We normally make an UserControl out of it with some general controls such as button to implement the common functionality. One piece of xaml code is something like this:
<WindowsFormsHost Name="windowsFormsHost1" >
<WindowsFormsHost.ContextMenu>
<ContextMenu>
<MenuItem Header="_Test1" />
</ContextMenu>
</WindowsFormsHost.ContextMenu>
<AxOWC:AxPivotTable x:Name="pivotTable" />
</WindowsFormsHost>
....
The AxPivotTable is a OWC (office web component) control. In another UserControl, we add a ReportViewer inside the WindowsFormsHost. Note that normally the AxPivotTable or ReportViewer has its default context menu even without any ContextMenu item I add.
So far my customized ContextMenu doesn't get showed yet (still the default one shows). Thanks to this question, I figured out I still need to capture the the mouse down event in the code-behind and set
windowsFormsHost1.ContextMenu.IsOpen = True
to show the contextmenu (weird though).
Now my problem is, only this Test1 ContextMenu is here now. The default ContextMenu won't show any more. As I mentioned, what we want is to add the customized on top of those default context menus.
this would be my working "Solution" for your example above i don't know if you need this also for Child Elements from your WindowsFormsHost. Hope this will help you
XAML
<Window x:Class="tete.MainWindow"
xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns="http://schemas.microsoft.com/netfx/2009/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms">
<Grid>
<av:WindowsFormsHost Name="myWFH">
<wf:ComboBox Name="myCBox">
<wf:ComboBox.ContextMenu>
<wf:ContextMenu>
<wf:ContextMenu.MenuItems>
<wf:MenuItem Text="somet"/>
</wf:ContextMenu.MenuItems>
</wf:ContextMenu>
</wf:ComboBox.ContextMenu>
</wf:ComboBox>
</Grid>
</Window>
Code-Behind
using System.Windows;
using System.Windows.Controls;
namespace tete
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var child = myWFH.Child as System.Windows.Forms.ComboBox;
child.ContextMenu.MenuItems.Add(new System.Windows.Forms.MenuItem("my new menuitem"));
}
}
}
EDIT
i hope now it fit's your needs :) and i understand correctly what you want ToDo
I would like to build a generic/re-usable modal dialog that I can use in our WPF (MVVM) - WCF LOB application.
I have a Views and associated ViewModels that I would like to display using dialogs. Bindings between Views and ViewModels are done using Type-targeted DataTemplates.
Here are some requirements that I have been able to draft:
I prefer this to be based on a Window instead of using Adorners and controls that act like a modal dialog.
It should get its minimum size from the content.
It should center on the owner window.
The window must not show the Minimize and Maximize buttons.
It should get its title from the content.
What is the best way to do this?
I usually deal with this by injecting this interface into the appropriate ViewModels:
public interface IWindow
{
void Close();
IWindow CreateChild(object viewModel);
void Show();
bool? ShowDialog();
}
This allows the ViewModels to spaw child windows and show them modally on modeless.
A reusable implementation of IWindow is this:
public class WindowAdapter : IWindow
{
private readonly Window wpfWindow;
public WindowAdapter(Window wpfWindow)
{
if (wpfWindow == null)
{
throw new ArgumentNullException("window");
}
this.wpfWindow = wpfWindow;
}
#region IWindow Members
public virtual void Close()
{
this.wpfWindow.Close();
}
public virtual IWindow CreateChild(object viewModel)
{
var cw = new ContentWindow();
cw.Owner = this.wpfWindow;
cw.DataContext = viewModel;
WindowAdapter.ConfigureBehavior(cw);
return new WindowAdapter(cw);
}
public virtual void Show()
{
this.wpfWindow.Show();
}
public virtual bool? ShowDialog()
{
return this.wpfWindow.ShowDialog();
}
#endregion
protected Window WpfWindow
{
get { return this.wpfWindow; }
}
private static void ConfigureBehavior(ContentWindow cw)
{
cw.WindowStartupLocation = WindowStartupLocation.CenterOwner;
cw.CommandBindings.Add(new CommandBinding(PresentationCommands.Accept, (sender, e) => cw.DialogResult = true));
}
}
You can use this Window as a reusable host window. There's no code-behind:
<Window x:Class="Ploeh.Samples.ProductManagement.WpfClient.ContentWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clr-namespace:Ploeh.Samples.ProductManagement.WpfClient"
xmlns:pm="clr-namespace:Ploeh.Samples.ProductManagement.PresentationLogic.Wpf;assembly=Ploeh.Samples.ProductManagement.PresentationLogic.Wpf"
Title="{Binding Path=Title}"
Height="300"
Width="300"
MinHeight="300"
MinWidth="300" >
<Window.Resources>
<DataTemplate DataType="{x:Type pm:ProductEditorViewModel}">
<self:ProductEditorControl />
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding}" />
</Window>
You can read more about this (as well as download the full code sample) in my book.
I'm answering my own question to help others find all answers I struggled to find in one place. What above seems like a straight forward problem, actually presents multiple problems that I hope to answer sufficiently below.
Here goes.
Your WPF window that will serve as the generic dialog can look something like this:
<Window x:Class="Example.ModalDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ex="clr-namespace:Example"
Title="{Binding Path=mDialogWindowTitle}"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner"
WindowStyle="SingleBorderWindow"
SizeToContent="WidthAndHeight"
ex:WindowCustomizer.CanMaximize="False"
ex:WindowCustomizer.CanMinimize="False"
>
<DockPanel Margin="3">
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" FlowDirection="RightToLeft">
<Button Content="Cancel" IsCancel="True" Margin="3"/>
<Button Content="OK" IsDefault="True" Margin="3" Click="Button_Click" />
</StackPanel>
<ContentPresenter Name="WindowContent" Content="{Binding}"/>
</DockPanel>
</Window>
Following MVVM, the right way to show a dialog is through a mediator. To use a mediator, you typically require some service locator as well. For mediator specific details, look here.
The solution I settled on involved implementing an IDialogService interface that is resolved through a simple static ServiceLocator. This excellent codeproject article has the details on that. Take note of this message in the article forum. This solution also solves the problem of discovering the owner window via the ViewModel instance.
Using this interface, you can call IDialogService.ShowDialog(ownerViewModel, dialogViewModel). For now, I'm calling this from the owner ViewModel, meaning I have hard references between my ViewModels. If you use aggregated events, you will probably call this from a conductor.
Setting the minimum size on the View that will eventually be displayed in the dialog doesn't automatically set the minimum size of the dialog. Also, since the logical tree in the dialog contains the ViewModel, you can't just bind to the WindowContent element's properties. This question has an answer with my solution.
The answer I mention above also includes code that centers the window on the owner.
Finally, disabling the minimize and maximize buttons is something WPF can't natively do. The most elegant solution IMHO is using this.
I've got an application based on Prism.
This is my shell:
<Window x:Class="AvarioCRM3.ShellV2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.codeplex.com/CompositeWPF" >
<DockPanel LastChildFill="True">
<Border
Padding="10"
DockPanel.Dock="Top"
Background="#ddd">
<DockPanel>
<ItemsControl
Name="MainNavigationPanel"
cal:RegionManager.RegionName="MainNavigationPanel"
DockPanel.Dock="Top"/>
</DockPanel>
</Border>
</DockPanel>
</Window>
In my MenuModule I add a view to the region and it shows fine:
public void Initialize()
{
MainNavigationPresenter mainNavigationPresenter = this.container.Resolve<MainNavigationPresenter>();
IRegion mainRegion = this.regionManager.Regions["MainNavigationPanel"];
mainRegion.Add(new TestView());
}
The problem is: I don't want an ItemsControl in my shell, I want a ContentControl, but when I use a ContentControl, it shows nothing.
Why would ItemsControl show my views and ContentControl show nothing?
Could this be because a ContentControl will only display a single child, whereas an ItemsControl has multiple children?
I have't worked with Prism, but the API suggests that an IRegion is expected to have multiple children. If you're using a ContentControl then it is a little ambiguous what happens when I do the following:
IRegion mainRegion = this.regionManager.Regions["MainNavigationPanel"];
mainRegion.Add(new TestView());
mainRegion.Add(new SecondTestView());
Unlike the ItemsControl with a ContentControl you also need to activate the view once you have added it to make it visible.
MainNavigationPresenter mainNavigationPresenter = this.container.Resolve<MainNavigationPresenter>();
IRegion mainRegion = this.regionManager.Regions["MainNavigationPanel"];
TestView view = new TestView()
mainRegion.Add(view);
mainRegion.Activate(view);
I noticed you are doing this in Initialize. Could be too early? Have you tried using registration rather than injection of your view to see if that changed anything?
regionManager.RegisterViewWithRegion("MainNavigationPanel", typeof(TestView));
This won't solve your problem, however it will prove that the problem is trying to add something before your region is actually available. RegisterViewWithRegion will delay the creation and display of the view until the region is available.