WPF Basics: Shared global styles for MVVM - c#

I am attempting to use an MVVM-ish approach to my WPF development.
I have my logical view model classes under the ViewModel namespace, and I have matching styling for these view model classes under the View namespace.
For now I have my View information in ResourceDictionary XAML files, as DataTemplates and Styles, which are all merged into the single App.Resources ResourceDictionary in app.xaml.
However, I'm running into a sort of chicken/egg problem. I want there to be global styles that I use all over the place. For example, I want my own custom text style called MonkeyText which could be used in various stylings all over the place. I can't just set this in the app.xaml file, because the resourcedictionarys that will want to use MonkeyText are included by that app.xaml file.
I guess if that's impossible an alternative would be to use UserControls instead of mostly using DataTemplates to establish my views? I'm afraid that using UserControls would tie the VM and V parts too closely together.

WPF provides DynamicResources for just this reason. StaticResources - which most resemble 'traditional' references in programming - have just the problem you encountered; they need to be defined and loaded prior to the point that a style is parsed. DynamicResources, on the other hand, do not need to be defined prior to the point they are used - indeed, you can even create them on the fly. WPF takes care of ensuring that DynamicResources are automatically loaded by all of the styles that reference them once they are actually loaded.
Using DynamicResources is straightforward. When you create your MonkeyText style, create it as you normally would:
<Style TargetType="TextBlock" x:Key="MonkeyText">
<Setter Property="TextAlignment" Value="Center"/>
<!-- etc. -->
</Style>
And then refer to it from elsewhere using a DynamicResource:
<TextBlock Text="Hello, World!" Style="{DynamicResource MonkeyText}"/>
If, for any reason, WPF cannot resolve your DynamicResource, it will fail silently without any exception thrown (StaticResources do throw exceptions when the cannot be resolved). It will, however, print a debug message when this happens - so keep an eye on the Output window in Visual Studio.
Since DynamicResources work with resources that are loaded at any point in any order, you can structure your resource dictionaries any way you like - so putting them in with your other View styles and merging them in via the single App.Resources ResourceDictionary in app.xaml will work fine.
More details on DynamicResources can be found in the MSDN docs for WPF.

Related

Approach to using resources in a user control without relying on App.xaml

I converted an application to a DLL library where my main window became a user control. I was using the method of creating a System.Windows.Application object manually to store my resources but I want move away from that and have my user control be self sufficient, so I can simply do something like:
CustomUserControl control = new CustomUserControl ( object_to_pass);
It will then take care of everything else internally. The basic layout of the control is a frame that hosts multiple pages, like a wizard style app.
I am having two main issues:
Setting up references to the view models
I thought that instead of using System.Windows.Application.FindResource which I extensively used, I will use a similar function on the user control class and pass a reference to my user control around via a singleton.
To do this I use mvvm-light's SimpleIoc container in a class called 'ViewModelLocator' to keep track of all the view models. Problem is, this was a resource in App.xaml loaded from the datacontext binding of the user control.
This cannot be done anymore as the user control itself has to instantiate the resources containing it further along in its own xaml:
<UserControl x:Class="WUP.Views.WarmUpPluginUserControl"
mc:Ignorable="d"
...
...
<!--This will not work-->
DataContext="{Binding Source={StaticResource Locator}, Path=MainWindowLogic}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Path/To/ViewModelLocator/Resource.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
Now I have to instantiate the ViewModel locator in the user control code behind and set it up as a resource with Resources.Add. This forces me to switch to dynamic resource for all references to the ViewModel locator from all other views (Pages). Not only does this cause issues described further on, its ugly as I can no longer access member function with the path like I used to:
DataContext="{Binding Source={StaticResource Locator}, Path=MainWindowLogic}">
Referencing resources from xaml, Dynamic vs Static
The resources I use are brushes, colours, templates and converters, each in their own resource dictionary, and I add them in the right order to avoid dependency issues.
The method in the first part works ok for accessing resources from the ViewModel via the reference to the user control in the singleton. The problem now is how to have the resources loaded in each view of the app. I tried the brute force method of sticking them all in Page.Resources or UseControl.Resources but that gave me resource not found errors in some pages despite them existing there. I am looking into why this happens but I am not sure
I then tried Dr.WPF's method of creating a singleton class that you can use to create a single instance of resources and expose them as a dependent property. This forces me to use dynamic resources again for all my views.
This is fine for all my resources except the converters, and I get errors for all converters originally referenced in this way:
Visibility="{Binding Functions.DictatesActions, Converter={StaticResource BooleanToVisibilityConverter}, UpdateSourceTrigger=PropertyChanged}"
So I don't know how to deal with this is the dynamic scenario.
I am seriously thinking to abandon this approach and just use System.Windows.Application to store all my resources, despite it potentially causing issues with other user controls in the hosting application (winforms). Please let me know if there is a better way!
I finally managed to fix my issues:
Setting up references to the view models
Here I just had to do it all from the code behind. As I mentioned I used a ViewModelLocator to keep track of all my VMs, so I set up the references as resources in the actual user control constructor:
Resources["Start"] = view_model_locator.Start;
Resources["SelectUnit"] = view_model_locator.SelectUnit;
Resources["HardwareChecks"] = view_model_locator.HardwareChecks;
Resources["ConfigurationChecks"] = view_model_locator.ConfigurationChecks;
...
I then included the reference to the user control in the ViewModel locator as static property:
ViewModelLocator.WarmUpPluginUserControl = this;
Then I could access it from the other views in their code behind like this:
DataContext = ViewModelLocator.WarmUpPluginUserControl.FindResource("Start");
I could also use it in the VMs in the same way that I used the Application.Current.FindResource(). It's not the most elegant solution, but it worked
Referencing resources from xaml, Dynamic vs Static
Here I stuck with the brute force method of including all the resources at the top of every page:
<WUP:WUPPage.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/WarmUpPlugin;component/Resources/Colors.xaml"/>
<ResourceDictionary Source="pack://application:,,,/WarmUpPlugin;component/Resources/Styles.xaml"/>
...
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
This did cause issues for me in the beginning where I had resource not found errors, but this was due to some of my dictionaries having dependencies other ones so I had to add them in to the relevant dictionaries via MergedDictionaries.
I did not notice this dependency issue when the application was standalone as all the resources required were already loaded in the application scope.
I intend to replace this with the Dr WPF method, but I would still have to change all my XAML references to dynamic from static then deal with the converters not being able be accessed via dynamic resource references.

WPF hide slider track

I'm using WPF (and the MVVM framework) to create an interface which has a slider on it.
<Slider Value="{Binding MotorDemandSpeed}" Maximum="3500" />
I'm trying to hide the track part on the slider so that you are left with just the 'thumb tack'. This is what the slider currently looks like (styles are controlled by a theme):
I've looked around at various methods, however I can't find a method that changes only a single slider.
Help is appreciated.
You need to set the Template property of this particular Slider instance to be able to override its ControlTemplate:
<Slider Value="{Binding MotorDemandSpeed}" Maximum="3500">
<Slider.Template>
<ControlTemplate TargetType="Slider">
<!-- define the custom template without a track here... -->
</ControlTemplate>
</Slider.Template>
</Slider>
In order to change the appearance of a control you will need to modify the control template. Each control is made up of many parts, and each part many objects. You can modify individual parts (such as the track) with the correct x:Key and TargetType.
This Question has an example of modifying a scrollbar control template, which is most likely similar to the template of this slider you have. The first step would be to identify the Xaml file in your theme which this slider uses and find the parts that define the trackbar, thumb, etc. From there you should be able to recreate the control to your liking, or just completely remove parts you do not need.
Are you using any third party controls that may have information on how to edit their themes? Perhaps try investigating Modifying Control Templates to get a better understanding of control templates.
Here is the MDSN page for the slider control template, you may find this useful.

C# MVVM How to dynamically create Views

I'm trying to create a memory game while strictly following the MVVM pattern to learn how to use it. Now I have a problem with creating views at run time.
I've created the following project structure:
Model project
-MemoryCardModel30
-Card
ViewModel project
-MainWindowViewModel
-CardViewModel
View project
-CardView
StartApplication project
-MainWindowView
The dependencies are as follows: StartApplication project -> View project -> ViewModel project -> Model project
After clicking a button on the MainWindowView the ICommand function for that button within the MainWindowViewModel will load a MemoryCardModel30 instance from the Model project. For each Card within the MemoryCardModel30 instance a CardViewModel will be created.
Now to the problems I face: How to create the CardView instances, how to link their DataContexts to the CardViewModels and how to arrange/assign the CardViews on the MainWindowView? The ViewModel project can't create Views as it has no dependency to the View project (would create a circular dependency and breaks the pattern). How to solve this issue while following the MVVM pattern?
P.S.: The CardViews need to be positioned exactly by x and y pos. which will require some complicated calculations which should go tho the corresponding CardViewModel. So some basic layouts like grid will not be sufficient I think.
Display them in an ItemsControl. I'm assuming that MainWindowViewModel.Cards is ObservableCollection<CardViewModel>.
<ItemsControl
ItemsSource="{Binding Cards}"
>
<!--
This creates UI for each item. There are other ways, if you've got a collection
of heterogeneous item types.
-->
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:CardViewModel">
<views:CardView />
</DataTemplate>
</ItemsControl.ItemTemplate>
<!--
Make it use a Canvas to be the actual container for the items, so we can control
their position arbitrarily, instead of the default StackPanel that just stacks
them up vertically.
-->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!--
The ItemsControl will put the instantiated item templates in ContentPresenters
that it creates. The positioning attributes have to go on the ContentPresenters,
because those are the direct children of the Canvas. The ContentPresenters are
the "item containers". You can customize them via the ItemContainerStyle property
of the ItemsControl.
-->
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<!--
The datacontext will be CardViewModel.
Bind Canvas.Left and Canvas.Top to appropriate properties
of CardViewModel. I'll assume it's got Point Position { get; }
A much better, more "pure MVVM" way to do this is for the items to
provide some kind of abstraction, maybe row/column or something else,
and either place them in a Grid or UniformGrid or some other kind of
dynamic layout control, or else convert that abstraction into Canvas
coordinates with a value converter on the Binding.
Then you can display the same item objects in different ways at the same
time without locking them into one layout.
Don't drive yourself crazy striving for ideological purity at the expense
of getting code out the door, but do consider redesigning that part.
-->
<Setter Property="Canvas.Left" Value="{Binding Position.X}" />
<Setter Property="Canvas.Top" Value="{Binding Position.Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
This is the canonical Way to Do It in WPF/MVVM. Use DataTemplates to create view instances of the appropriate type. The viewmodel is in charge of what objects should be presented to the user; the views are responsible for how they're shown. You don't need or want any MVVM framework for this. The built-in DataTemplate features of WPF are enormously powerful. Don't trust anybody who thinks you need anything else for a project within two orders of magnitude of this size.
I think I misunderstood your question. I originally thought you were asking how to display a new window for specific view models. While this answer won't specifically apply to you, I'll leave it up, as it is tangentially related. It may help others confused about what to search for.
I have a ViewManager class that links view types to viewmodel types. One of the methods on it is ShowViewFor that handles this task, it takes a viewmodel instance and:
Looks up the view for that viewmodel type.
Creates an instance of that view.
Sets the DataContext of that view instance to the viewmodel that was passed in.
Shows the view.
It also does a bunch of other tasks like tracking open views, displaying message boxes and dialogs, etc.
The ViewManager is available though an IOC container via an interface, so it can be mocked up for unit tests.
I'm sure there are many existing frameworks out there that do this, but like you, I wanted to learn MVVM from "the roots up".

MahApps PasswordBox not showing the clear button

I'm using the latest MahApps WPF toolkit and I'm having a bit of trouble with applying my own styles onto its controls, precisely the PasswordBox.
I've defined the styles in a separate .xaml file which is included in the App.xaml file so that it is globally visible, across all .xaml files.
But when I use the style specified under a key the MahApps ClearTextButton refuses to appear on the control.
Here is my style:
<Style x:Key="DefaultPasswordBoxStyle"
TargetType="{x:Type PasswordBox}"
BasedOn="{StaticResource ResourceKey={x:Type PasswordBox}}">
<Setter Property="HorizontalContentAlignment">
<Setter.Value>Center</Setter.Value>
</Setter>
<Setter Property="FontSize">
<Setter.Value>16</Setter.Value>
</Setter>
</Style>
And its usage in a separate .xaml file:
<PasswordBox Margin="{StaticResource ControlMargin}"
controls:TextBoxHelper.ClearTextButton="True"
Style="{StaticResource DefaultPasswordBoxStyle}"
Width="200" />
If I delete the Style attribute the button shows as it is supposed but want to be able to apply my own styles also. It is actually visible in the XAML designer, which is funny. I tried DynamicResource, as well, but with the same results.
So to continue on from the original comments, you had a Type set as your Resource instead of pointing to a Key name. So once you gave your new custom template a proper path to find the original Style template as a BasedOn condition to inherit the rest of the MahApps style then the issue was resolved.
The VS underlining/not locating the resource thing, well I run into that all the time and I'm kind of convinced it's a defect in VS though I wouldn't mind knowing how to clear that off as well. It's annoying since sometimes it will have those errors in the IDE, sometimes it won't, lots of fun.
In the meantime, now that you have your template referencing the correct one as its base with the resource dictionaries referenced properly then all is well in the world again. Hope this helped, cheers!

Is it possible to use reflection directly in XAML

In my DB I have a table that contains different items of userControls with the attributes "ClassName", "AssemblyName" and "NameSpace" which are necesarry to init the instances using reflection.
My Idea was To get this collection from the DB, set the collection as the data-context and dynamically load these usercontrols into a tabcontrol. I could use a "tabItem" which would contain it and in runtime in the code behind load it. I guess it would be really handy and fancy if it could be done directly from XAML in a template.
I've been googleling for something similar, but found nothing without using code behind.
I was thinking something like the following
<TabControl.ContentTemplate>
<DataTemplate>
<xxxControl ClassName="{Binding ClassName}" AssemblyName="{Binding AssemblyName}" NameSpace="{Binding NameSpace}" />
</DataTemplate>
</TabControl.ContentTemplate>
I could make such a custom "xxxControl" but it would be a waste of time if something like that already exists. This way The GUI could be completly generated by the parameters in the DB.
You can do a lot of things in XAML using markup extensions, in this case you could create one that instantiates controls from the given information. For that it needs some dependency properties that can be bound, and in ProvideValue it would then get the assembly, construct the full name and instantiate it.
Usage:
<DataTemplate>
<me:Instance Assembly="{Binding AssemblyName}"
NameSpace="{Binding NameSpace}"
Class="{Binding ClassName}"/>
</DataTemplate>
Obviously you still have code-behind, but that is how it should be, imperative code does not belong in XAML at all.
Also i doubt that your data-base should contain information about UI controls...
Ugh. Don't control your UI from the database directly. The closest you should come (assuming you can't make significant architecture changes) IMO would be to load your DB entries into an IObservable in your VM and use a DataTemplateSelector to translate your collection into UI controls.

Categories

Resources