How to implement custom interfaces in WPF UserControl and avoid dynamic casts? - c#

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>

Related

"Kproj.Forms.frmSwitch" cannot be the root of a XAML file because it was defined using XAML

I am aware that there are other questions posted that appear to be the same issue but none of them fix my issue.
I'm new to WPF I'm trying to convert a program from WinForms to WPF. I have a main window, "Kproj.Forms.frmLogin", that inherits a base class, "Kproj.Forms.frmSwitch", that inherits the System.Windows.Window class. WhenI got the above issue, my initial XAML code was:
<Control:frmSwitch x:Class="Kproj.Forms.frmLogin"
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:Control="clr-namespace:Kproj.Forms"
mc:Ignorable="d"
Title="LOG IN" Height="309" Width="678">
<Grid Height="271" Width="662">
... Content
</Grid>
</Control:frmSwitch>
with these in the code-behinds:
namespace Kproj.Forms
{
public partial class frmLogin : frmSwitch
{
}
}
namespace Kproj.Forms
{
public partial class frmSwitch : Window
{
}
}
Upon further research, I found out that I needed to make frmSwitch into a base class with no XAML. Thus, I created frmSwitch2 in just general Class form that looks like this:
namespace Kproj.Forms
{
public class frmSwitch2 : Window
{
}
}
I then adjusted the main window XAML accordingly:
<Control:frmSwitch2 x:Class="Kproj.Forms.frmLogin"
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:Control="clr-namespace:Kproj.Forms"
mc:Ignorable="d"
Title="LOG IN" Height="309" Width="678">
<Grid Height="271" Width="662">
... Content
</Grid>
</Control:frmSwitch2>
and the main window code-behind to:
namespace Kproj.Forms
{
public partial class frmLogin : frmSwitch
{
}
}
Now, I lost the original error message, but I gained a message that states
"The name 'frmSwitch2' does not exist in the namespace 'clr-namespace:FITS.Forms'."
even though it suggests "frmSwitch2" when I type "Control:" in the main window XAML, so it knows it does exist in the namespace. Any suggestions?
Disclaimer: I tried researching it as best as possible but all the posts I found on StackOverflow were this issue but all were fixed by converting from XAML\cs partial classes to solo code-behind full XAML-less class.
After looking into what the inheritance actually wanted, I learned that the only purpose of the inheritance was for variables so I was able to make it work but just converting them to static global variables and accessing them directly. I ended up not needing the inheritance after all.
If anyone else, that ends up knowing more MVVM, does come across a fix for this issue, it would be nice to know it. Even though I no longer need it, it would be good learning.

Global Code on Window Initialisation

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.

Add control to custom base page

I'm trying to create a custom base page for my WP app. I'm doing this by creating a new cs class file that inherits from PhoneApplicationPage like this:
public class BasePage: PhoneApplicationPage
{
//handle common non-visual stuff here
}
The problem is that I would like to add a control on every page that uses BasePage but BasePage has no LayoutRoot or any visual element that I can attach the control to. Is there a way I can add the same control to all the pages that use BasePage so that I don't have to copy/paste it every time?
Edit: Adding XAML based on TriggerPin's answer. I've created a new XAML file for my BasePage.cs but this is throwing errors in BasePage.g.cs:
"MyApp.MainPage already contains a definition for '_contentLoaded'" (and similar message for 'LayoutRoot' and 'InitializeComponent')
<local:BasePage
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyApp.Template"
x:Class="MyApp.MainPage"
mc:Ignorable="d">
Notice that by default, types derived from PhoneApplicationPage are partial
public partial class MainPage : PhoneApplicationPage
The partial definition allows you to split the class definition across multiple files. Most commonly with this type, the definition is split between a .cs and .xaml file.
<phone:PhoneApplicationPage x:Class="MyProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone">
<!-- Custom layout here -->
</phone:PhoneApplicationPage>
If you make your implementation a partial class and provide base xaml code, you will be able to do what you want to achieve.
You need a container which must be Panel type at least for adding your children controls. A Page is just a UserControl which couldn't add your UIElement into it directly.
Try this:
Panel container = // init it from your page, you must have a root element in page.
container.Children.Add(new TextBlock()); // your children control.
I did it in Win8.

WPF Open a new View from the ViewModel

This is my first WPF-MVVM application, and this is my structure:
One project with my app.xaml to open the application and override the OnStartup to resolve the MainWindow. (I did that due to the references);
One project for my Views;
One project for my ViewModels;
One project for my Model.
And I have de following problem: I'm on the MainWindowView and I click on a button to show another view. How am I supposed to do to open this another view from my MainWindowViewModel whereas my View Project has reference with the ViewModel Project, and I can't reference the ViewModel Project with the View Project?
By the way, I'm using Unity for the dependency injection.
So, could you help me?
There are several approaches to that.
You can define a dialog/navigation/window service interface, defined in the ViewModels project. You will need to decide how the ViewModels will express which window they want to open. I generally use an IDialogViewModel interface, which some of my ViewModels implement, and pass an instance of the ViewModel to the service, but you can use an enum, string, whatever you want, so your implementation can map to the real window which will be opened.
For example:
public interface IDialogService
{
bool? ShowDialog(object dialogViewModel);
}
ViewModels that want to open new Windows would receive an instance of that service and use it to express the intention of opening a Window. In your Views project, you would define a type which implements your service interface, with the real logic behind opening the Window.
Following the example:
public class DialogService : IDialogService
{
private Stack<Window> windowStack = new Stack<Window>();
public DialogService(Window root)
{
this.windowStack.Push(root);
}
public bool? ShowDialog(object dialogViewModel)
{
Window dialog = MapWindow(dialogViewModel);
dialog.DataContext = dialogViewModel;
dialog.Owner = this.windowStack.Peek();
this.windowStack.Push(dialog);
bool? result;
try
{
result = dialog.ShowDialog();
}
finally
{
this.windowStack.Pop();
}
return result;
}
}
Your main project will be responsible for creating and injecting the dialog service in the ViewModels who need it. In the example, the App would create a new dialog service instance passing the MainWindow to it.
A similar approach to do it would be using some form of the messaging pattern (link1 link2 ).
In addition, if you want something simple you can also make your ViewModels raise events when they want to open Windows and let the Views subscribe to them.
EDIT
The complete solution that I use in my apps a generally a bit more complex, but the idea is basically that. I have a base DialogWindow, which expects a ViewModel which implements an IDialogViewModel interface as DataContext. This interface abstracts some functionalities you expect in dialog, like accept/cancel commands as well as a closed event so you can also close the window from the ViewModel. The DialogWindow consists basically in a ContentPresenter which Content property is bound to the DataContext and hooks the close event when the DataContext is changed (and a few other things).
Each "dialog" consists in an IDialogViewModel and an associated View (UserControl). To map them, I just declare implicit DataTemplates in the resources of the App. In the code I've shown, the only difference would be there wouldn't be a method MapWindow, the window instance would always be a DialogWindow.
I use an additional trick to reuse layout elements between dialogs. On approach is to include them in the DialogWindow (accept/cancel buttons, etc). I like to keep the DialogWindow clean (so I can use it event to "non-dialog" dialogs). I declare a template for a ContentControl with the common interface elements, and when I declare a View-ViewModel mapping template, I wrap the View with a ContentControl with my "dialog template" applied. You can then have as much "Master templates" for your DialogWindow as you want (like a "wizard like" one, for example).
Straight forward approach
If I understand you correctly MainWindowView is resolved through Unity when the app starts, which resolves its dependency on MainWindowViewModel?
If that is the flow you are using I would suggest continuing on the same path and letting the MainWindowView handle the opening of the new view via a simple click handler for the button. In this handler you can then resolve the new view, which would resolve that view's view model and then you're back in MVVM land for the new view as well.
That solution is straight forward and would work perfectly fine for most smaller applications.
Heavier approach for more complex apps
If you don't want that kind of view-first flow I would suggest introducing some kind of controller/presenter that coordinates the views and view models. The presenter is responsible for making decisions about if/when to actually open/close views and so on.
This is a quite heavy abstraction though, which is more suitable for more complex applications, so make sure you actually get enough benefit out of it to justify the added abstraction/complexity.
Here is a code sample of what this approach might look like:
public partial class App
{
protected override void OnStartup(StartupEventArgs e)
{
var container = new UnityContainer();
container.RegisterType<IMainView, MainWindow>();
container.RegisterType<ISecondView, SecondWindow>();
container.RegisterType<IMainPresenter, MainPresenter>();
container.RegisterType<ISecondPresenter, SecondPresenter>();
var presenter = container.Resolve<IMainPresenter>();
presenter.ShowView();
}
}
public interface IMainPresenter
{
void ShowView();
void OpenSecondView();
}
public interface ISecondPresenter
{
void ShowView();
}
public interface ISecondView
{
void Show();
SecondViewModel ViewModel { get; set; }
}
public interface IMainView
{
void Show();
MainViewModel ViewModel { get; set; }
}
public class MainPresenter : IMainPresenter
{
private readonly IMainView _mainView;
private readonly ISecondPresenter _secondPresenter;
public MainPresenter(IMainView mainView, ISecondPresenter secondPresenter)
{
_mainView = mainView;
_secondPresenter = secondPresenter;
}
public void ShowView()
{
// Could be resolved through Unity just as well
_mainView.ViewModel = new MainViewModel(this);
_mainView.Show();
}
public void OpenSecondView()
{
_secondPresenter.ShowView();
}
}
public class SecondPresenter : ISecondPresenter
{
private readonly ISecondView _secondView;
public SecondPresenter(ISecondView secondView)
{
_secondView = secondView;
}
public void ShowView()
{
// Could be resolved through Unity just as well
_secondView.ViewModel = new SecondViewModel();
_secondView.Show();
}
}
public class MainViewModel
{
public MainViewModel(MainPresenter mainPresenter)
{
OpenSecondViewCommand = new DelegateCommand(mainPresenter.OpenSecondView);
}
public DelegateCommand OpenSecondViewCommand { get; set; }
}
public class SecondViewModel
{
}
<!-- MainWindow.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" Height="350" Width="525">
<Grid>
<Button Command="{Binding OpenSecondViewCommand}" Content="Open second view" />
</Grid>
</Window>
<!-- SecondWindow.xaml -->
<Window x:Class="WpfApplication1.SecondWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SecondWindow" Height="350" Width="525">
<Grid>
<TextBlock>Second view</TextBlock>
</Grid>
</Window>
This article presents a similar solution to what I've used in production before.
To open new window from MainWindowView, you need to pass reference of Frame component or whole window to MainWindowViewModel object(You can do this when binding command to transition button or something, pass them as object), there you can navigate to new page, however, if there is nothing of special that you need to do in ViewModel when transitioning, you can just use classic ButtonClick event or w/e in MainWindowView.cs that will do navigation for you, that is ok for basic transitions.
P.S. I am not sure why you use different projects for ViewModels/Views/Models.

Understanding WPF deriving WIndow class

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).

Categories

Resources