I'm sure this is easy, but new to me for WPF using C#. I know about inheriting from classes and have done so many times such as in C# WinForms projects...
public class MyClass : DerivedFromClass
{}
However, stumped in WPF and here's the issue. I want to build my own set of controls to be used as a baseline for a new learning project... preset my own styles, colors, backgrounds, and other functionality. No problem. Start first with a WPF Window and create "MyWindow".
Now, I want to take this baseline "MyWindow" and subclass THAT for yet another class of MySubClassedWindow. So, I create a new Window class, and by default, VS2010 builds the both designer and code portions of the form. I do view code on the MySubClassedWindow and find
partial class MySubclassedWindow : Window
{}
In C# using WinForms, I would just change to (and I've included the class library reference that includes the "MyWindow" declaration.
partial class MySubclassedWindow : MyWindow
{}
When I do, I get a compilation error of
Partial declarations of 'MyNameSpace.MySubclassedWindow' must not specify different base classes
Your base class should just be a class file (not a Window).
So create WindowBase.cs
public class WindowBase : Window
{
// ...
}
In MainWindow (for example) change the xaml.cs file to inherit from WindowBase instead
public partial class MainWindow : WindowBase
{
public MainWindow()
{
InitializeComponent();
}
// ...
}
In MainWindow.xaml, include the namespace for WindowBase and change Window to base:WindowBase like this
<base:WindowBase x:Class="SubclassWindow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:base="clr-namespace:NamespaceForWindowBase"
Title="MainWindow" Height="350" Width="525">
<!--...-->
</base:WindowBase>
Having a base Window class brings a critical drawback, namely that binding to properties in your base class is much more difficult to do (and the currently accepted answer does not solve this problem). What's the point of inheriting if you cannot reference base properties? I have figured out how to set this up after some long hours, and wanted to share in the hopes that others will be spared this pain.
You may need to use things like value converters, which can only be referenced via static binding, which in my case made sense to have in the WindowBase class. I have included an example because I found it difficult to use these converters consistently in both design and run mode.
You cannot set the x:Name property of this inherited Window via XAML, but you may not need to do so if using the below approach. I have included an example of how to set the name, because inheriting from Window will not allow you to set the name at design time in the subclass. I do not recommend relying on the name of the window at design time, but setting d:DataContext should take care of any binding needs for you.
Be warned that in design mode, but not run mode, a copy of WindowBase (or the class specified in d:DataContext) will be instantiated in design mode and used as the binding context. So in very specific cases you may see data discrepancies, but in the vast majority of use cases this approach should suffice.
WindowBase.cs
````
public class WindowBase : Window
{
//User-Defined UI Configuration class containing System.Drawing.Color
//and Brush properties (platform-agnostic styling in your Project.Core.dll assembly)
public UIStyle UIStyle => Core.UIStyle.Current;
//IValueConverter that converts System.Drawing.Color properties
//into WPF-equivalent Colors and Brushes
//You can skip this if you do not need or did not implement your own ValueConverter
public static IValueConverter UniversalValueConverter { get; } = new UniversalValueConverter();
public WindowBase()
{
//Add window name to scope so that runtime properties can be referenced from XAML
//(Name setting must be done here and not in xaml because this is a base class)
//You probably won't need to, but working example is here in case you do.
var ns = new NameScope();
NameScope.SetNameScope(this, ns);
ns["window"] = this;
//Call Initialize Component via Reflection, so you do not need
//to call InitializeComponent() every time in your base class
this.GetType()
.GetMethod("InitializeComponent",
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance)
.Invoke(this, null);
//Set runtime DataContext - Designer mode will not run this code
this.DataContext = this;
}
//Stub method here so that the above code can find it via reflection
void InitializeComponent() { }
}
SubClassWindow.xaml
<local:WindowBase
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:YourProjectNamespace"
x:Class="YourProjectNamespace.SubClassWindow"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type= {x:Type local:WindowBase}, IsDesignTimeCreatable=True}"
Title="SubClassWindow" Height="100" Width="300">
<!--Design-time DataContext is set in d:DataContext. That option does not affect runtime data binding
Replace local:WindowBase with local:SubClassWindow if you need to access properties in SubClassWindow-->
<Grid Background="{Binding UIStyle.BackgroundColor, Converter={x:Static local:WindowBase.UniversalValueConverter}}"></Grid>
</local:WindowBase>
Nothing is needed in the SubClassWindow code behind (not even a constructor).
Related
I have several WPF UserControls. Each of these controls implement an interface IStepEditor
The class declaration is as follow:
public partial class EditorLoadCsv : UserControl, IStepEditor { ... }
IStepEditor is not very important, but it is currently defined as follow:
public interface IStepEditor
{
StepConfig Save();
void Load(StepConfig config);
}
I then have several classes holding those controls, like this:
public class StepConfigController
{
public IStepEditor EditorControl { get; }
}
All of this is working as expected when I use IStepEditor all around, but to add those controls to a WPF window from a StepConfigController, I eventually have to cast them in a UserControl, like this:
((StackPanel)panEditorControl).Children.Add(currentControl as UserControl);
Since the IStepEditor implementation is actually a UserControl, it works. I could add checks for that cast, but the whole idea seems wrong. I cast an object to another very unrelated object through some weak relationship I trust others to follow.
I tried to create an abstract class inheriting from both UserControl and IStepEditor and have my actual UserControls derive from that, but since the WPF UserControls are partial classes, it didn't work. It was something like this:
public abstract class StepEditorControl : UserControl, IStepEditor
{
abstract public StepConfig Save();
abstract public Load(StepConfig config);
}
So this class was legit and compiled, but trying to derive from it in WPF failed.
public partial class EditorLoadCsv : StepEditorControl
{
}
This code generates:
error CS0263: Partial declarations of 'EditorLoadCsv' must not specify different base classes
Which is right, because the xaml markup still references a UserControl. Trying to change the xaml markups from UserControl to a StepEditorControl failed, but it could be the proper solution to the whole problem. I also tried to implement a WPF Custom Control Library, but it seemed like a lot of work just to implement an interface with 2 methods.
The XAML is generated automagically through Visual Studio designer:
<UserControl x:Class="MyNamespace.EditorLoadCsv"
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:MyNamespace"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="623.077">
<!--- Actual UserControl content ---!>
</UserControl>
So an answer to either of these questions could solve my problem:
1- How to avoid a (possibly) dangerous dynamic cast when implementing WPF UserControl with custom interfaces?
2- How to get WPF XAML markup to accept a custom class instead of UserControl?
3- How to refactor code to avoid this ugliness in the first place?
The Panel.Children property is a UIElementCollection, so anything that should be added needs to be a UIElement, which is the base class of all WPF UI components.
Do not use the as operator, but simply cast your editor objects to UIElement. If any of them is by accident not a UIElement, you would correctly get an InvalidCastException.
((Panel)panEditorControl).Children.Add((UIElement)currentControl);
In order to create a UserControl-derived base class for all your StepEditors, you did the first step correctly:
public abstract class StepEditorControl : UserControl, IStepEditor
{
public abstract void Load(StepConfig config);
public abstract StepConfig Save();
}
However, the XAML of a control derived from StepEditorControl would have to look like this:
<local:StepEditorControl
x:Class="MyNamespace.EditorLoadCsv"
xmlns:local="clr-namespace:MyNamespace"
...>
...
</local:StepEditorControl>
How to determine the DataContext of a specific control at design time in a WPF application?
DataContext might be specifically set in XAML, inherited or set somewhere in code and sometimes it is hard to figure out at design time which class the bindings are referring to.
What I usually try to do to find the DataContext class is to search the binding name in VS. For example I see the binding is something like
ItemSource = {Binding Items}
...I will search the text "Items" in order to get to the class but VS sometimes fails to find the searched text (I have several projects in the solution).
I would like to add an approach to StepUpĀ“s listing:
The design instance:
Just like you can define a run time data context, you can add a data context that is specifically for the design time via:
<Usercontrol x:Class="MyUserControl"
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:viewModels="clr-namespace:MyProject.ViewModels"
d:DataContext="{d:DesignInstance viewModels:MyViewModel}"
d:DesignHeight="300"
d:DesignWidth="600"
mc:Ignorable="d">
</UserControl>
In Visual Studio you can then use IntelliSense for bindable properties and if your view model has an uncomplicated or even parameterfree constructor, it will be created in the Designer and you can test trigger or converters without having to start your application.
DataContext of Control is ViewModel. So there are many ways to set ViewModel for DataContext of View, and if you find your ViewModel, but there is no Items property in ViewModel, then it means that you should add such property to get work binding.
Also, I recommend you to see Debug->Windows->Output window where you can see binding info. That is you can know binding errors.
To conclude, I would like to show ways of setting ViewModel to DataContext:
There are many approaches to set DataContext:
The first approach. In view:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
The second approach. You should override OnStartUp() method of App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow app = new MainWindow();
ProductViewModel context = new ProductViewModel();
app.DataContext = context;
app.Show();
}
}
The third approach. In constructor of Windows:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext=new MainWindowViewModel();
}
}
The fourth approach. You can set DataContext through DependencyInjection by UnityContainer or another IoC container. But DependencyInjection, Prism and UnityContainer are other questions and goes from this scope of the question. Just for example:
protected override void RegisterTypes()
{
unityContainer.RegisterType<object, ItemControl>("ModuleAUpper");
unityContainer.RegisterType<IViewModelItemControl, ViewModelItemControl>();
unityContainer.RegisterTypeForNavigation<ItemControl>();
}
So, the goal is to create a new custom window in XAML (with things in it), that I can use in another XAML designer as the root node (and the workaround is to do this). This used to make it possible (and there are a number of resources still out there mistakenly saying as much), but was changed and will now get you the error: "xyz cannot be the root of a XAML file because it was defined using XAML".
Microsoft tersely states:
You need to define your base class all in code without using XAML.
This is further explained by others that it is because the XAML designer would not know where to put content in the custom window; which is reasonable, as there are things in its ContentControl already.
So, that got me thinking.
If I create my custom MyWindow in xaml like so:
<Window x:Class="MyStuff.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyWindow">
<Grid>
<UserControl Name="ContentContainer"/>
<!-- some undefined content here to justify this exercise -->
</Grid>
</Window>
And in code, hide the Window's content and point it to a new empty System.Windows.ContentControl (the UserControl's ContentControl in this example):
using System.Windows;
namespace MyStuff
{
public partial class MyWindow : System.Windows.Window
{
// hide our window content with new keyword
public new object Content
{
get { return ContentContainer.Content; }
set { ContentContainer.Content = value; }
}
public Window()
{
InitializeComponent();
}
}
}
...Why am I still forbidden from doing this?
Why is the default compiler behavior not to require Content to be explicitly overwritten to point to another suitable location in this circumstance?
And in implementations which do not define where content should go explicitly, why not have the default functionality be to replace whatever is in the ContentControl in the parent control as content with a warning anyway (and only throw an error when the parent window's ContentControl is inaccessible)?
I think you are getting mixed up with what content actually is.
The Content of the window is everything inside the window element, this includes your UserControl.
There are a few options for you.
Create a dependency property for additional content and have a ContentControl or ContentPresenter bind to this property. See here.
Create a Style for your Window, where you can define what goes around the Content of the Window using a ControlTemplate. See here.
A combination of both 1 and 2, which would be a Custom Control, here you will inherit from Window, and define it's style and control template. See here.
So I'm pretty new to WPF and MVVM, and while I understand the premise, a lot of this stuff is like trying to read hieroglyphs for me.
Basically, my situation is this: I'm using Activiz, a c# wrapper for VTK, which is an image processing/visualization library. So, in this library, there's a WinForms control called vtk:RenderWindowControl, which is an opengl control containing the class that handles all of the visualization functionality. I think it'd be easier to just use WinForms, but that's not really an option for me.
So, to use vtk:RenderWindowControl in a WPF application, I just need to shove it into a WindowsFormsHost and then I can use it more or less just like the example code, in the code behind (if that's the correct term for the .xaml.cs file)
That's fine for a test app, but in practice, I'd like to follow MVVM if possible. This is where I've run into a wall. If "renderControl" lives in the View class, how can I reference it and use it from the ViewModel? I think binding is the answer to that question, but I only really know how to do that for simple types and commands.
Following ideas in another thread I found, I managed to set up something like this answer
My codebehind looks like this:
public partial class RenderPanel_View : UserControl
{
public static readonly new DependencyProperty RWControlProperty =
DependencyProperty.Register("RWControl", typeof(RenderWindowControl), typeof(RenderPanel_View), new PropertyMetadata(null));
public RenderWindowControl RWControl
{
get { return (RenderWindowControl)GetValue(RWControlProperty); }
set { SetValue(RWControlProperty, value); }
}
public RenderPanel_View()
{
// This is necessary to stop the rendercontrolwindow from trying to load in the
// designer, and crashing the Visual Studio.
if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) {
this.Height = 300;
this.Width = 300;
return;
}
InitializeComponent();
this.RWControl = new RenderWindowControl();
this.RWControl.Dock = System.Windows.Forms.DockStyle.Fill;
this.WFHost.Child = this.RWControl;
}
}
My .xaml looks like this
<UserControl x:Class="vtkMVVMTest.RenderPanel.RenderPanel_View"
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:vtk="clr-namespace:Kitware.VTK;assembly=Kitware.VTK"
xmlns:rp="clr-namespace:vtkMVVMTest.RenderPanel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
RWControl="{Binding VMControlProperty}">
<Grid>
<WindowsFormsHost x:Name ="WFHost"/>
</Grid>
</UserControl>
So two things. One, That last line of the xaml header is an error, "The member 'RWControl' is not recognized or accessible". I don't really understand why. Second, for what I'm guessing is the ViewModel half of the equation, how is VMControlProperty supposed to be defined?
Am I at least on the right track here, or is this way off base?
Some controls are not MVVM friendly and you have make the ViewModel aware of View interface and allow interact with it directly. Do not open the whole control to the ViewModel it will ruin the ability to write tests, put an interface on top for example IRenderPanelView and open in the interface only the functionality you need to access from ViewModel. You can then create a DP property of this type in the view, set it in the constructor and bind it to ViewModel.View property in xaml.
Any way to create a global piece of code to run on the initialisation of all Windows like there you can create global properties for XAML through App.xaml?
I'm just curious as the piece of code I'm using relates specifically to interface but can't be set in xaml so must be in code so I have to write it into the constructor of each Window. Just wondering if there might be a work around.
you can solve this problem with the concept of inheritance create Base class that inherits Window do your common stuff in that class . All of Windows that want this common functionality will inherit the base class.
Base class
public class MyBaseWindow : Window
{
//do your common stuff in this base class for all windows
protected object MyProperty { get; set; }
}
.cs
public partial class MyWindow : MyBaseWindow
{
xaml
<local:MyBaseWindow x:Class="WpfApplication1.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MyWindow" Height="300" Width="300">
I hope this will give you an idea.