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.
Related
I have an interface IFooBar and some concrete implementations of it, FooBarOne and FooBarToo.
public interface IFooBar
{
int Value { get; set; }
}
public class FooBarOne : IFooBar { ... }
public class FooBarTwo : IFooBar { ... }
I've added a DependencyProperty (called FooBar) of type IFooBar to a custom control MyControl.
public static readonly DependencyProperty FooBarProperty = ...
public IFooBar FooBar
{
get { return (IFooBar)GetValue(FooBarProperty ); }
set { SetValue(FooBarProperty, value); }
}
Whenever this control is used, I can create instances of FooBarOne or FooBarTwo as static resources, and then use these to set the FooBar DependencyProperty on instances of MyControl and this all works as expected.
<UserControl.Resources>
<ResourceDictionary>
<ns:FooBarOne x:Key="MyFooBarOne" Value="1"/>
<ns:FooBarTwo x:Key="MyFooBarTwo" Value="2"/>
</ResourceDictionary>
</UserControl.Resources>
...
<controls:MyControl FooBar="{StaticResource MyFooBarOne}"/>
<controls:MyControl FooBar="{StaticResource MyFooBarTwo}"/>
What I'm struggling with is that I now need to bind a value from a ViewModel to the the IFooBar.Value property.
I tried adding the following to my resouces:
<UserControl.Resources>
<ResourceDictionary>
<ns:FooBarOne x:Key="MyFooBarOne" Value="{Binding SomeViewModelProperty}"/>
<ns:FooBarTwo x:Key="MyFooBarTwo" Value="{Binding SomeViewModelProperty}"/>
</ResourceDictionary>
</UserControl.Resources>
But this doesn't work because IFooBar.Value isn't a DependencyProperty.
I realise I could probably add a new DependencyProperty for IFooBar.Value to my MyControl, but in reality IFooBar actually contains numerous properties and I wanted to avoid having to create a new DependencyProperty for each of the properties on IFooBar.
Is there a way for me to bind a ViewModel property onto the the properties of my IFooBar instances?
Is there a way for me to bind a ViewModel property onto the the properties of my IFooBar instances?
No, for you to be able to bind something to a property in XAML, the target property must be a dependency property.
In the below sample markup, Value is the target property and SomeViewModelProperty is the source property:
<ns:FooBarOne x:Key="MyFooBarOne" Value="{Binding SomeViewModelProperty}"/>
Again, the target property must be a dependency property for you to be able to bind a value to it.
I presume that you're using MVVM, if so, then you should have properties on your ViewModel that return an IFooBar rather than declaring static resources. Then you can just return those values. If you need to specifically create an IFooBar for a given value, then you can create a ValueConverter that takes your value and spits out a FooBarOne or FooBarTwo.
I have several UserControls that should display the same data. Each UserControl has a different layout of the data that is to be presented. The ContentPresenter can bind to any one of the UserControls by using a DataTemplate in my Resources and by binding the Content to a StyleViewModel. Each UserControl is associated with a ViewModel as defined in the DataType of the DataTemplate. The ViewModels associated with any given UserControl all inherit from the StyleViewModel. The UserControls should get their data from a SettingsViewModel. The UserControls appear in the main Window.
The problem is that I can't figure out how to make the data from the SettingsViewModel accessible to the UserControls.
Is it possible to pass a reference to a SettingsViewModel to the constructor of one of these UserControls that are displayed using a ContentPresenter?
Is there another way to easily switch between different views of the data (i.e. my UserControls) without using a ContentPresenter? If so, how would I make the data accessible to the UserControls?
The following is code from my SingleLineViewModel.cs:
public class SingleLineViewModel : StyleViewModel
{
public SingleLineViewModel() { }
}
The other ViewModels are similar. They are essentially empty classes that inherit from StyleViewModel, so that I can bind to a Style property which is of type StyleViewModel in my SettingsViewModel. The StyleViewModel is also an essentially empty class that inherits from ViewModelBase.
The following is code from my Resources.xaml:
<ResourceDictionary <!--other code here-->
xmlns:vm="clr-namespace:MyProject.ViewModel"
<!--other code here-->
<DataTemplate DataType="{x:Type vm:SingleLineViewModel}">
<vw:ucSingleLine/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SeparateLinesViewModel}">
<vw:ucSeparateLines/>
</DataTemplate>
<!--other code here-->
</ResourceDictionary>
The following is code from SettingsViewModel.cs:
public class SettingsViewModel : ViewModelBase
{
// other code here
private StyleViewModel _style;
public StyleViewModel Style
{
get { return _style; }
set
{
if (value != _style && value != null)
{
_style = value;
OnPropertyChanged("Style");
}
}
}
// other code here
public SettingsViewModel()
{
_style = new SingleLineViewModel();
}
// other code here
}
The following is code from my MainView.xaml:
<ContentPresenter Name="MainContent" Content="{Binding SettingsVM.Style}"/>
You might find that you are trying to do much at once. Consider how you might test this scenario? Or how would you walk the data in a Debugger to check its state? Good practice recommends that your data is separate from your UI elements. An MVVM pattern such as you are trying to use normally provides the view models to help transition the data from it's simple data into forms that the UI can use.
With that in mind, I would recommend that you try do develop a ViewModel tier that presents all the data without the UI holding it together, i.e. instead of trying to inject the additional SettingsViewModel into your controls you should make your viewmodels hold everything they need.
It looks like you are off to a good start, your SettingsViewModel lets you get hold of a Style, but your style doesn't seem to have any data. So why not pass it in the constructor.
public SettingsViewModel()
{
_style = new SingleLineViewModel(WhatINeedForStyle);
}
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.
I am trying to access commands that are defined in MainWindow.xaml in another window. I am only able to get grayed out titles of these commands. I am wondering what should be should be done in order to get a full access.
Sample of the command:
public partial class MainWindow : Window
{
public static RoutedUICommand AddCommand1 = new RoutedUICommand("Command ", "command1", typeof(MainWindow));
public MainWindow()
{
InitializeComponent();
this.CommandBindings.Add(new CommandBinding(AddCommand1, AddCommand1Executed));
}
private void AddCommand1Executed(object sender, ExecutedRoutedEventArgs e)
{
AddNewItem picker = new AddNewItem();
picker.ShowDialog();
}
I access these command in style through databinding:
<Menu x:Name="TaskMenuContainer"><MenuItem x:Name="menuItem" Header="TASKS" ItemsSource="{Binding}" Template="{DynamicResource TaskMenuTopLevelHeaderTemplateKey}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding}" />
<Setter Property="Header" Value="{Binding Text, RelativeSource={RelativeSource Self}}" />
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource Self}}"/>
</Style>
</MenuItem.ItemContainerStyle>
These commands work in pages that is loaded inside MainWindow.xaml through frame. However, if I have pop up window that is not part of MainWindow.xaml these commands are only grayed out and not functional anymore (cannot be executed). Any advice is highly appreciated!
The way you define the command, you define it for a particular window. If you want to handle the command globally, at the application level, you can use CommandManager.RegisterClassCommandBinding:
First, define you command in a separate static class:
public static class GlobalCommands
{
public static RoutedUICommand AddCommand1 = new RoutedUICommand("Command ", "command1", typeof(MainWindow));
}
Then, in you window or whatever place you want to put the command logic, register the command handlers:
public partial class MainWindow : Window
{
static MainWindow()
{
CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(GlobalCommands.AddCommand1, AddCommand1Executed, CanAddExecute));
}
private static void AddCommand1Executed(object sender, ExecutedRoutedEventArgs e)
{
AddNewItem picker = new AddNewItem();
picker.ShowDialog();
}
}
And in your menu style you should change the binding to x:Static:
<Setter Property="Command" Value="{x:Static my:GlobalCommands.AddCommand1}" />
When the command is routed, when checking for command bindings in each active element in the UI, the bindings registered for each element's class will also be checked. By registering the binding here, you can cause every instance of a class to be able to handle the specific command.
So, in the above example, the type Window is used and this will cause the routing to find the command binding in any instance of Window, once the routing reaches that instance in its search for a command binding.
You could instead, for example, restrict the handling to a specific subclass of Window, so that the command will only be bound in an instance of that subclass. Or you can use some other UI element type, so that that the presence of that specific type of element will cause the event to be handled. Just set the owning type for the registered command binding appropriately for your specific needs.
I want to combine binding from my custom data context which contains ViewModel class and ResourceProvider class. Custom data context is set as window DataContext.
I use it that way:
<Button x:Name="btnShow" Content="Show" Command="{Binding View.HandleShow}"/>
Which View is property from dataContext. I want to use localization by custom data context using minimum markup and set ResourceProvider from other source in code that I created my own data context
Is there any possibility to do it in something which is similar to that line of code:
<TextBlock Text="{Binding Res.Key=test}" />
My resource provider inherits from markup extension with one Property: Key.
Thanks for any advice
You can create a custom markup extension using the following code :
public class LocalizedBinding : MarkupExtension
{
public String Key { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
//use target.TargetObject and target.TargetProperty to provide value based on Key
}
}
and use it like :
<TextBlock Text="{local:LocalizedBinding Key=SomeKey}" />
I try that solution but i prefer avoid prefix local because localizedBinding came from different source and use IoC pattern because of that I create CustomDataContext