Usual WPF architecture:
public partial class MainWindow: Window {
... InitializeComponent()
}
XAML: <Window x:Class="MainWindow"> </Window>
What I want to move to:
public abstract class BaseWindow: Window {
public System.Windows.Controls.TextBlock control1;
public System.Windows.Shapes.Rectangle control2;
public System.Windows.Controls.TextBox control3;
}
public partial class AWindowImplementation {
... InitializeComponent()
}
public partial class AnotherWindowImplementation{
... InitializeComponent()
}
XAML:
<BaseWindow x:Class="AWindowImplementation"> </BaseWindow>
<BaseWindow x:Class="AnotherWindowImplementation"> </BaseWindow>
The above is pseudo-code. This new architecture compiles, with warnings that the implementations hide the control defintions (because the place where I should put the 'override' keywords are withing the auto-generated InitializeComponent). Unfortunately the control fields don't get populated.
Is this achievable? What I am trying to do is create several UI designs with the same interface/controls so that the rest of the code can interact with either design.
EDIT: Thanks to pchajer and Yevgeniy, I now have the following working solution, but I still get override warnings:
public class MainWindowBase : Window
{
public TextBlock control1;
public Rectangle control2;
public TextBox control3;
static MainWindowBase()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MainWindowBase),
new FrameworkPropertyMetadata(typeof(MainWindowBase)));
}
public override void OnApplyTemplate()
{
control1 = (TextBlock) FindName("control1");
control2 = (Rectangle) FindName("control2");
control3 = (TextBox) FindName("control3");
}
}
<Style TargetType="{x:Type views:MainWindowBase}"
BasedOn="{StaticResource {x:Type Window}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type views:MainWindowBase}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public partial class AWindowImplementation :MainWindowBase {
... InitializeComponent()
}
<MainWindowBase x:Class="AWindowImplementation"> </MainWindowBase>
I guess I will have to use different field names in the base class to get rid of the warnings, or perhaps remove InitializeComponent in the derived class. But anyway it works now.
In WPF you can create a Base class which inherits from Window and that has a XAML. But there is a workground
refer this link - How to create a common WPF base window style?
I'm not sure how you would expect that pseudo-code to work as nothing is calling your InitializeComponent. Ordinarily, WPF calls it on your behalf in your window's constructor. But in your case you're adding a new implementation (not an override) and nothing is calling it.
One option is to just call your new implementation from each subclass constructor. eg. AWindowImplementation's constructor could call this.InitializeComponent().
Another option is for BaseWindow to define a virtual method (say, InitializeComponentCore) that its constructor calls. Base classes can then override that method.
You need to define base class for Window as a custom control. Just create a new custom control, set base class to Window and then insert style from blend (you may add yours components). See also answer from pchajer.
Related
I changed my UserControl to be a ReactiveUserControl and now I can't view the Design View. Is there anything I can do to get the designer to work with ReactiveUserControl?
The Visual Studio designer has issues when your control or window directly inherits from a generic class. This was a pretty common issue with WinForms as well. You can work around this issue by defining another non-generic class that sits between the generic ReactiveUserControl and your control:
public partial class MyUserControl : MyUserControlBase
{
public MyUserControl()
{
InitializeComponent();
}
}
public abstract class MyUserControlBase: ReactiveUserControl<MyUserControlViewModel>
{
}
In the XAML, our root object element is defined as the base element (MyUserControlBase) and its class declaration is connected to the partial class defined above (MyUserControl):
<myNameSpace:MyUserControlBase
x:Class="MyNameSpace.MyUserControl"
xmlns:myNameSpace="clr-namespace:MyNameSpace"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
I have created user control MyUserControl. Now I want to create custom control MyCustomControl that derives from MyUserControl. MyCustomControl.cs code is following:
public class MyCustomControl : MyUserControl
{
public MyCustomControl()
{
this.DefaultStyleKey = typeof(MyCustomControl);
}
}
I have Themes/Generic.xaml file with the style
<Style TargetType="local:MyCustomControl">
...
</Style>
Instantiating MyCustomControl at runtime I get ArgumentException executing the line
this.DefaultStyleKey = typeof(MyCustomControl);
What am I missing?
Assigning a type that derives from UserControl to DefaultStyleKey is explicitly disallowed by throwing a ArgumentException (why an ArgumentException and why no explanitory message is included only the SL team know).
A UserControl cannot be templated receiving instead its own associated Xaml. Thats the whole point of UserControl. You need to convert MyUserControl into a templatable control was well if you wish to inherit off it in manner you are attempting.
Is it possible to provide a default style for a generic base control in WPF?
Assume I have the following base classes:
public abstract class View<T> : ContentControl
where T : ViewModel
{
static View()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(View<T>),
new FrameworkPropertyMetadata(typeof(View<T>)));
}
// Other properties, methods, etc in here
}
public abstract class ViewModel
{
// Other properties, methods, etc in here
}
Then assume I have a two classes which inherit from these base classes:
public partial class TestView : View<TestViewModel>
{
public TestView()
{
InitializeComponent();
}
// TestView specific methods, properties, etc
}
public class TestViewModel : ViewModel
{ /* TestViewModel specific methods, properties, etc */ }
Now I want to provide a default style for the base control that all my derived controls use:
<Style TargetType="{x:Type local:View`1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:View`1}">
<Border Background="Magenta"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<Button>Test</Button>
<ContentPresenter ContentSource="Content" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
However, when I use my TestView control, I don't have the template markup applied (and thus any content i might define in the XAML of my TestView control is not in the visual/logical tree).
I am basically trying to take my base view/viewmodel classes and apply a consistent look and feel. This of course works in the non-generic base view cases. However, I require the type-safe hook up between view and viewmodel so I can call methods on the viewmodel from anything that has reference to the view (which I know may not "sit well" with the way some people have implemented MVVM).
I found fairly simple solution involving a custom TypeExtension.
1 - Set the DefaultStyleKey to the default generic type as mentioned in CodeNaked's answer:
DefaultStyleKeyProperty.OverrideMetadata(typeof(View<T>),
new FrameworkPropertyMetadata(typeof(View<>)));
2 - Create the following class than inherits from System.Windows.Markup.TypeExtension
[System.Windows.Markup.ContentProperty("Type")]
public class TypeExtension : System.Windows.Markup.TypeExtension
{
public TypeExtension()
: base()
{ }
public TypeExtension(Type type)
: base(type)
{ }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (Type == null)
throw new InvalidOperationException("Must specify the Type");
return Type;
}
}
3 - Update the style's TargetType to point to the new local:Type extension instead of the usual x:Type extension
<Style>
<Style.TargetType>
<local:Type Type="{x:Type local:View`1}" />
</Style.TargetType>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
. . .
Thats it..
There is a caveat though, VS throws a compile error when you attempt to bind/set any of the dependency properties defined on the View<T> class. So you cannot use simple syntax like {TemplateBinding ViewTProperty} ...
Short answer: no
Long answer:
In your code behind you are specifying a DefaultStyleKey of typeof(View<T>), where T is resolved to an actual type. In the XAML, you are effectively doing typeof(Value<>), where T is still "undefined".
You can set your DefaultStyleKey to:
DefaultStyleKeyProperty.OverrideMetadata(typeof(View<T>),
new FrameworkPropertyMetadata(typeof(View<>)));
This will correctly find the Style, but will result in a exception (as TestView cannot be case to View<>).
Your best bet is to define your base Style like you do, but give it an x:Key like "ViewBaseStyle". Then create a Style for each derive type that is BasedOn ViewBaseStyle.
The way I did it is I made a base class without generics and templated that. Then I inherit the base class with a generic class (not templated), which can be used to make your class variations (also not templated). In effect, everything inheriting the base class (without generics) would have the same template.
For instance,
//You can define a template for this!
public class ViewBase : UserControl
{
public ViewBase() : base()
{
DefaultStyleKey = typeof(ViewBase);
}
}
//Use for class variations
public class View<T> : ViewBase
{
public View() : base()
{
//Do whatever
}
}
//Example class variation
public class DecimalView : View<decimal>
{
public DecimalView() : base()
{
//Do whatever
}
}
ViewBase, View<T>, and DecimalView now all share the same default style. Additionally, you can also specify an individual style for each class variation based on the original style (ViewBase), just not for the generic class.
It's worth noting that the best way, then, to bind to the top-level class' properties would be using the syntax {Binding Path, RelativeSource={RelativeSource AncestorType={x:Type ViewBase}}} versus {TemplateBinding Path}. The latter, as well as {Binding Path, RelativeSource={RelativeSource TemplatedParent}}, will only be applicable to properties owned by ViewBase.
I am learning Silverlight. In the process, I'm trying to build a custom user control. My ultimate goal is to be able to write the following statement in XAML:
<my:CustomControl>
<my:CustomControl.MainControl>
<Canvas><TextBlock Text="Hello!" /></Canvas>
</my:CustomControl.MainContent>
</my:CustomControl>
The content of the control will be wrapped in a custom border. Once again, this is just a learning exercise. To append my border, I have create the following UserControl:
<UserControl x:Class="CustomControl"
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">
<Grid x:Name="LayoutRoot">
<Border>
<!-- CustomControl Content -->
</Border>
</Grid>
</UserControl>
The code-behind for this file looks like the following:
public partial class CustomControl : UserControl
{
public UIElement MainContent
{
get { return (UIElement)GetValue(MainContentProperty); }
set { SetValue(MainContentProperty, value); }
}
public static readonly DependencyProperty MainContentProperty =
DependencyProperty.Register("MainContent", typeof(UIElement), typeof(CustomControl),
new PropertyMetadata(null));
public CustomControl()
{
InitializeComponent();
}
}
The thing I am having a problem with is getting the MainContent to appear in my CustomControl. I am confident that I am setting it properly, but I'm not sure how to display it. I really want it to be a DependencyProperty as well so I can take advantage of data binding and animations.
How do I get the MainContent to appear in the CustomControl? Thank you
First you need to wait until the rest of the control has been parsed so you need to hook the loaded event:-
public CustomControl()
{
InitializeComponent();
Loaded += new RoutedEventHandler(CustomControl_Loaded);
}
Then in the loaded event assign your MainControl property to the Child property of the border. To do that its best if you give your Border an x:Name which for now I'll simple call "border".
void CustomControl_Loaded(object sender, RoutedEventArgs e)
{
border.Child = MainControl;
}
That'll get you going. Of course you may need to deal with the MainControl property being changed dynamically so you need add a bool isLoaded field in your control and set that in the loaded event. When true your MainControl setter should assign the incoming value to the border.Child.
Things can start to get more complicated and in fact I don't recommend this approach to creating a custom control. For a better approach see Creating a New Control by Creating a ControlTemplate
I am new to WPF and have created a WPF User Control Library
I added a Base class that looks like this
public class TControl : UserControl
{
}
and want all of my controls to inherit from it.
I have a Control called Notification which looks like
public partial class Notification : TControl
{
public Notification()
{
InitializeComponent();
}
Works fine except when ever i recompile the hidden partial class where InitializeComponent() is defined gets regenerated and inherits from System.Windows.Controls.UserControl
this gives me an
Partial declarations of 'Twac.RealBoss.UserControls.Notification' must not specify different base classes
error,
is there anyway to force the generated class to inherit from my base class?
Your XAML file probably has:
<UserControl x:Class="YourNamespace.Notification" .... >
Try changing this to:
<Whatever:TControl x:Class="YourNamespace.Notification" xmlns:Whatever="clr-namespace:YourNamespace" />
The error you are getting is because the use of UserControl in the XAML tells the compiler to produce a partial class inheriting from UserControl, instead of inheriting from your class.
You can completely remove the ": TControl":
public partial class Notification : TControl
{
}
and write:
public partial class Notification
{
}
instead, since the base class is defined in the XAML part, as Paul wrote.