The MVVM way of presenting controls dynamically - wpf

I have a need to display some kind animation as different processes gets started. My initial idea was to simply add some <ContentControl> tags to the XAML and bind them to a property in the View Model object which then simply assigned this property a ProgressBar, some busy spinner or whatever.
This works but I don't like it. The primary reason I don't like it is because the View Model should not involve itself in presentation matters and this pattern clearly breaks that paradigm.
This is pretty much what my (ugly) code looks like atm:
XAML:
<ContentControl Content="{Binding ProcessAAnimation}" />
In View Model class:
public object ProcessAAnimation
{
get { return _processAAnimation; }
private set
{
_processAAnimation = value;
OnPropertyChanged("ProcessAAnimation");
}
}
public object IsProcessARunning
{
get { return _processARunning; }
private set
{
if (value == _processARunning)
return;
_processRunnings = value;
if (value)
ProcessAAnimation = SomeNiftyAnimationControl();
else
{
if (ProcessAAnimation is IDisposable)
((IDisposable)ProcessAAnimation).Dispose();
ProcessAAnimation = null;
}
}
}
// (clipped: More properties for "Process B", "Process C" and so on)
So, is there a better pattern to achieve this. Preferrably, a pattern where I can create my animation controls dynamically using XAML alone?
Please note that I have already tested a solution where I declare three different animation controls and then bind their Visibility property to the View Model state. That, however, is below par in my book because I don't want to just hide the controls, I want them to be gone unless needed. Besides, that would also make it impossible to dynamically use different types of animations for whatever needs may be.
Anyone?

Well your ViewModel knows about the operation and the progress itself. The rest can be accomplished via Triggers. At least thats the way we do it. So your ViewModel has a property "IsLoadingImage" for example, which is set when your viewmodel starts a BackgroundWorker for loading a big image, it also returns the progress reported by the BackgroundWorker "ImageLoadingProgress" now these two properties are enough to pass to your View. Your view, consists of a Progress bar or a custom control for your special animation. You could now bind the "IsLoadingImage" in a Trigger to toggle the ProgressBar/Animation control visibility and the Value of these is bound to "ImageLoadingProgress".
Like i said, thats how we handle it, and our application makes heavy use of MVVM.
Edit respond to a comment: How to change the template in a trigger
<ControlTemplate x:Name="ActiveTemplate" TargetType="{x:Type MyType}">
<!-- Template when active -->
</ControlTemplate>
<ControlTemplate x:Name="DeactivatedTemplate" TargetType="{x:Type MyType}">
<!-- Template when deactivated -->
</ControlTemplate>
<Style TargetType="{x:Type MyType}">
<Setter Property="Template" Value="{StaticResource DeactivatedTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Template" Value="{StaticResource ActiveTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
This assumes that MyType is a control that can has a ControlTemplate and that the DataContext has a Property IsActive to toggle the Template.

Related

How to access Properties set by "coder" inside say an overriding (named) Button Template in a xaml Dictionary

I am tearing my hair out trying to create a Style for the "Button bar" buttons in an App I am working on, but I want the developer to be able to specify the colors used in Button gradient fills etc by adding xaml code in their Button declaration for the BackGround, Foreground and BorderBrush colors.
I have used the "Copy Template" trick of a totally undefined to get a copy of the full default Template for the control, but am totally confused by the way all the important colors are Hard coded internally in the style, being renamed with names such as "Button.Static.Background" which are all using HARD CODED COLORS attached to them eg:
<SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/>
<SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/>''
and these are then referenced later on in the template by these names. What I would really like to be able to do is to refer to the User defined properties for these items rather than hard coded values at this point in the template, but I cannot seem to find a way to do so.
As an example, here is what I am trying, but it doesn't work, although I do not get any errors as such. The first line is a standard declaration, the next 2 are the ones I want to allow the user to override when using this style to match their preferred background/foreground color schemes.
<SolidColorBrush x:Key="Button.Static.Border" Color="Black"/>
<SolidColorBrush x:Key="Button.MouseOver.Background" Color="{DynamicResource Background}"/>
<SolidColorBrush x:Key="Button.MouseOver.Border" Color="{DynamicResource Border}"/>
Later on in the template we come to :-
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background"
TargetName="border"
Value="{StaticResource Button.MouseOver.Background}"/>
<Setter Property="BorderBrush"
TargetName="border"
Value="{StaticResource Button.MouseOver.Border}"/>
</Trigger>
'''
and this is where I want the user defined colors (if supplied) to be used when the mouseover occurs, rather than any hard coded values ?
.
Am I trying to be too clever here, or is there a sensible way I can achieve this ?
You can make a templated control based on nearly any control, this case being a button, and extend the control. You can add properties to allow users to specify the colors when using the control in the XAML. Of course, you’ll have to set default colors - likely being the colors you’ve used in your question.
Here’s an answer I wrote the other day on how to create a bindable control:
https://stackoverflow.com/a/66394791/11590704
The important part in the answer is the Dependency Properties and the Template property in the control’s Style. You don’t need to create a new control to use TemplateBinding.
You can use the same methods there and practically add any property you want. Foreground, MouseOverForeground, MouseOverBackground, etc.
An alternative would be to create a global theme. All your controls should use this theme (you’d likely have to create customized styles for each control to use the theme).
You would declare the default theme colors in a resource dictionary like you did in your question.
The benefit of a theme is that end users can customize the theme at run-time, e.g. change a button’s background color through the UI (you’d have to code in the theme adjustments in your .cs code). Also, your control will be visually coherent.
This is a larger task though but I’d keep it in mind when creating your controls. I’ve had end users who need high contrasting colors so I created a theme and some code for theme adjustments.
EDIT - More Information
You don't have to create dependency properties for the base properties (although I suppose you can if you really wanted to). You can work around them in either the XAML or the callbacks/events in your control's code-behind.
Example: Working around the base background property.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Set the default background
DefaultBackground = this.Background;
}
// Store the default background color to revert back to
private Brush DefaultBackground;
// Dependency property for mouse over background color
public static readonly DependencyProperty MouseOverBackgroundProperty = DependencyProperty.Register(
nameof(MouseOverBackground), typeof(Brush),
typeof(NavButton), new PropertyMetadata(Brushes.Transparent));
public Brush MouseOverBackground
{
get => (Brush)GetValue(MouseOverBackgroundProperty);
set => SetValue(MouseOverBackgroundProperty, value);
}
// On Mouse Enter => Set Background
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
// Set background color
this.Background = MouseOverBackground;
}
// On Mouse Leave => Revert Background
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
// Set background color back
this.Background = DefaultBackground;
}
I used the events already in the control to handle the background color states. This is a basic example using overrides for the event handlers but you can subscribe to the events directly.
And then your XAML will look like this:
<local:NavButton Background="Transparent" MouseOverBackground="Red"/>
<!-- OR -->
<SolidColorBrush x:Key="ButtonBackground" Color="#1AFFFFFF" />
<SolidColorBrush x:Key="ButtonMouseOverBackground" Color="#00897B" />
<local:NavButton Background="{DynamicResource ButtonBackground}"
MouseOverBackground="{DynamicResource ButtonMouseOverBackground}"/>
The triggers are handled in the code-behind.
EDIT 2: Some more information
I forgot to mention that triggers may not work unless you have default values set. You set the default values in the Style.
<Style TargetType="{x:Type local:NavButton}">
<!-- The default value -->
<Setter Property="Background" Value="Grey" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!-- The trigger value -->
<Setter Property="Backround" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>

WPF button styling depending on active view

I'm using Caliburn Micro to develop a simple MVVM WPF application.
My ShellView has a single ContentControl and three buttons each which bind to a public method in my ShellViewModel, lets say ActivateView1, ActivateView2 and ActivateView3.
My ShellViewModel inherits from Conductor and each Activate method calls ActivateItem(new View1ViewModel()), etc.
So far so good. When I click a button, a new view gets activated in the ContentControl. The problem is that I need each button to change style when its "associated view" is active and I have really no idea how to achieve this functionality. Do you have any suggestions?
I'm fairly new to Caliburn Micro and WPF-styling so any help will be much appreciated.
I am not very sure about this but still I can think of something like this,
you can create a style and add the style to your button. something like this
<Style x:Key="ButtonStyle1" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Fill" TargetName="yourButtonName" Value="Black"/>
<Setter Property="Margin" TargetName="yourButtonName" Value="5,0,5,0"/>
</Trigger>
</Style.Triggers>
</Style>
and you can add this style to your button.
I can think of two possible options you could use:
You could bind your buttons style properties to properties on you ShellViewModel. In those properties you can determine the style to return based on the shells Active view i.e.
return ActiveItem == button1ViewModel ?
(Style) App.Current.Resources["Button1ActiveStyleKey"] :
(Style) App.Current.Resources["Button1InactiveStyleKey"];
this would mean your ViewModel would be aware of Styles which you may not want. If that is the case option two would be to write a Caliburn.Micro IResult which changes the style of the button and return 3 of those (one for each button) from a Coroutine that is invoked via the button click i.e.
public IEnumerable<IResult> ButtonOneClicked()
{
yield return new ChangeButtonStyle("Button1Name", "Button1ActiveStyleKey");
yield return new ChangeButtonStyle("Button2Name", "Button2InactiveStyleKey");
yield return new ChangeButtonStyle("Button3Name", "Button3InactiveStyleKey");
}
The implementation of the ChangeButtonStyle IResult would search the view (provided via the ActionExecutionContext parameter to IResult.Execute) for a control with the name provided to the 1st parameter of ChangeButtonStyle ctor, and then set the style property of that control using the resource key provided as the 2nd paramter to the ChangeButtonStyle ctor.
You can use
<Trigger Property ="IsPressed" Value ="True">
I think it does the trick...

WPF/MVVM: Disable a Button's state when the ViewModel behind the UserControl is not yet Initialized?

I have a DocumentListView.Xaml with a ListBox and 3 Buttons.
Behind that UserControl sits a DocumentListViewModel with 3 Buttons and their Command Property bound to 3 RelayCommands.
I have 3 Controller like AdministrationController, BillingController, ReportController.
Every Controller has ObservableCollections like Customer 1 : N Order 1: N Document same for the other Controller.
In one Controller I have a special binding situation. When my DocumentListViewModel is not initialized by its parent ViewModel like OrderViewModel (because no orders are loaded/exist) then my UserControl has 3 buttons which are ENABLED. Ok the user can press the 3 buttons and nothing happens but still its very confusing and above all the consistency in my user interface is gone.
How can I set the Command of a Button as default to "Disabled" ?
Setting the Buttons IsEnabled property to false does not help because the button will stay forever in the disabled state. No CanExecute TRUE will set it to IsEnabled = true.
AND I do not want to introduce another property IsButtonEnabled... that stupid because then I have both worlds winforms and wpf behind my buttons logic... ICommand should be enough.
Or you can use a Style for the button to disable:
<Style TargetType="{x:Type Button}" x:Key="DisablerButton">
<Style.Triggers>
<Trigger Property="Command" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</Trigger>
</Style.Triggers>
</Style>
This is an interesting situation. Honestly I've never run into the case where the UI was loaded and interactive but the ViewModel was not yet bound.
However, ignoring that for a moment, you could potentially use a FallbackValue on your binding to bind to a globally available NullCommand or something that always returns false for its CanExecute method.
<Button Command="{Binding SaveCommand, FallbackValue={StaticResource NullCommand}}" />

WPF style depending on checkbox state

I am creating a settings editor where plugin writers can define their own user interface for configuring their plugins. I am implementing a feature to hide certain "advanced" elements if a checkbox is unchecked.
The checkbox XAML is trivial:
<CheckBox Name="isAdvanced">_Advanced</CheckBox>
Ideally (more on this later), implementors would just add a flag to advanced controls (which should be hidden when the "advanced" checkbox is unchecked) like so:
<Button library:MyLibraryControl.IsAdvanced="True">My Button</Button>
The problem lies in making the magic of hiding the IsAdvanced="True" elements when isAdvanced.IsChecked == false. I have the desired behaviour with this style on the window element:
<Window.Resources>
<Style TargetType="Button">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding (library:MyLibraryControl.IsAdvanced), RelativeSource={RelativeSource Mode=Self}}" Value="True" />
<Condition Binding="{Binding IsChecked, ElementName=isAdvanced}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="UIElement.Visibility" Value="Collapsed" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
However, this method presents two problems:
It only adds functionality to buttons and nothing else. The IsAdvanced flag can (should be able to) be added to any visual element.
It replaces/overrides the styles which would otherwise be on the button.
Is there some other way to produce the functionality I want? I'm not afraid of working in the code-behind, but an elegant XAML solution is ideal (as this is purely a UI change, aside from saving the state of the checkbox in the user's preferences).
Some other methods of signifying advanced elements have come to mind. These include using a dynamic resource and directly binding:
<Button Visibility="{DynamicResource IsAdvancedVisibility}">My Button</Button>
<Button Visibility="{Binding IsChecked, RelativeSource={...}, ValueConverter={...}}">My Button</Button>
Using a resource dictionary would probably work, but it seems like a really bad solution as UI state doesn't seem like it should belong in a dictionary. Binding manually is quite the mess because the state of the checkbox has to be sent somehow to the element, and aside from hardcoding values I don't see it not becoming a mess.
Both of these alternate solutions tie semantics ("this is an advanced option") to appearance ("advanced options should be collapsed"). Coming from the HTML world, I know this is a very bad thing, and I refuse to submit to these methods unless absolutely necessary.
How about moving this into the ViewModel instead of XAML because this looks like behavior to me.
The behavior you want seems to me - each plugin registers a bunch of properties (mapping to UI Controls) as advanced. There is a global setting to turn on/off advanced properties. When this happens, update all plugins to show/hide their advanced properties
Have plugin writers implement an interface containing a set only property AreAdvancedControlsVisible. Let them take care of hiding/showing the controls in their UI via property change handler. The advanced UI controls can bind to a ShowAdvancedControls flag on the pluginVM, which is toggled on/off from the prop changed handler.
The framework can just loop over the available plugins and set this flag whenever the ShowAdvanced checkbox is set.
There are probably alot of better ways to solve this problem but I tried to work past the two issues you had with your solution. Small sample project with this can be downloaded here.
1.It only adds functionality to buttons and nothing else. The
IsAdvanced flag can (should be able
to) be added to any visual element.
Adding an Attached Property, that make all children inherit the value, to the top-most container could fix this.
2.It replaces/overrides the styles which would otherwise be on the
button.
Bea Stollnitz has a nice blog article about merging Styles here.
It has an extension method for Style called Merge which could be used.
Sounded pretty straight forward but the following problems made the code more complex.
1. The Visual elements doesn't have a style when the Attached Property is inherited. Required Loaded event.
2. A Style can't be modified when it is in use. Required a copy method for the Style.
So, we want this Style to be merged with the active Style for all children in the parent container.
<Style x:Key="IsAdvancedStyle">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding (library:MyLibraryControl.IsAdvanced), RelativeSource={RelativeSource Mode=Self}}" Value="True" />
<Condition Binding="{Binding IsChecked, ElementName=isAdvanced}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="Control.Visibility" Value="Collapsed" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
If the root container is a StackPanel we then add this. The style IsAdvancedStyle will then be inherited by all the children and merged with the active Style.
<StackPanel local:StyleChildsBehavior.StyleChilds="{StaticResource IsAdvancedStyle}">
StyleChildsBehavior.cs
public class StyleChildsBehavior
{
public static readonly DependencyProperty StyleChildsProperty =
DependencyProperty.RegisterAttached("StyleChilds",
typeof(Style),
typeof(StyleChildsBehavior),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.Inherits,
StyleChildsCallback));
public static void SetStyleChilds(DependencyObject element, Style value)
{
element.SetValue(StyleChildsProperty, value);
}
public static Style GetStyleChilds(DependencyObject element)
{
return (Style)element.GetValue(StyleChildsProperty);
}
private static void StyleChildsCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (DesignerProperties.GetIsInDesignMode(d) == true)
{
return;
}
Style isAdvancedStyle = e.NewValue as Style;
if (isAdvancedStyle != null)
{
FrameworkElement element = d as FrameworkElement;
if (element != null)
{
if (element.IsLoaded == false)
{
RoutedEventHandler loadedEventHandler = null;
loadedEventHandler = new RoutedEventHandler(delegate
{
element.Loaded -= loadedEventHandler;
MergeStyles(element, isAdvancedStyle);
});
element.Loaded += loadedEventHandler;
}
else
{
MergeStyles(element, isAdvancedStyle);
}
}
}
}
private static void MergeStyles(FrameworkElement element, Style isAdvancedStyle)
{
if (element != null)
{
Style advancedStyle = GetStyleCopy(isAdvancedStyle);
advancedStyle.Merge(element.Style);
element.Style = advancedStyle;
}
}
private static Style GetStyleCopy(Style style)
{
string savedStyle = XamlWriter.Save(style);
using (MemoryStream memoryStream = new MemoryStream(Encoding.ASCII.GetBytes(savedStyle)))
{
ParserContext parserContext = new ParserContext();
parserContext.XmlnsDictionary.Add("library", "clr-namespace:HideAll;assembly=HideAll");
return XamlReader.Load(memoryStream, parserContext) as Style;
}
}
}
After this the IsAdvancedStyle will be merged in all children of the StackPanel and this goes for children that are added in run-time as well.
Modified Merge extension method from the blog link.
public static void Merge(this Style style1, Style style2)
{
if (style1 == null || style2 == null)
{
return;
}
if (style1.TargetType.IsAssignableFrom(style2.TargetType))
{
style1.TargetType = style2.TargetType;
}
if (style2.BasedOn != null)
{
Merge(style1, style2.BasedOn);
}
foreach (SetterBase currentSetter in style2.Setters)
{
style1.Setters.Add(currentSetter);
}
foreach (TriggerBase currentTrigger in style2.Triggers)
{
style1.Triggers.Add(currentTrigger);
}
}
I decided to invert the problem a little bit, and it worked well.
Instead of dealing with styles, I used property binding as suggested by Gishu. However, instead of placing the UI in the VM (where properties would propagate several layers manually), I used an attached property named ShowAdvanced which propagates down via property inheritance.
Creating this property is trivial:
public static readonly DependencyProperty ShowAdvancedProperty;
ShowAdvancedProperty = DependencyProperty.RegisterAttached(
"ShowAdvanced",
typeof(bool),
typeof(MyLibraryControl),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior
)
);
The checkbox sets the ShowAdvanced property above on the entire window. It could set it elsewhere (e.g. on the grid), but putting it on the window makes more sense IMO:
<CheckBox Grid.Column="0"
IsChecked="{Binding (library:MyLibraryControl.ShowAdvanced), ElementName=settingsWindow}"
Content="_Advanced" />
Changing the visibility (or whatever other properties desired) depending on the ShowAdvanced property becomes easy:
<Foo.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Foo.Resources>
<Button Visibility="{Binding (library:MyLibraryControl.ShowAdvanced), RelativeSource={RelativeSource Self}, Converter={StaticResource BooleanToVisibilityConverter}}">I'm Advanced</Button>
Ditching styles allows plugin writers to completely change the layout of their controls if they need to. They can also show advanced controls but keep them disabled if desired. Styles brought up a lot of problems and, as Meleak showed, the workarounds were messy.
My main problem with putting the 'advanced' display logic in the VM is that it is now less likely you can get away with binding multiple views to the same VM while maintaining the flexibility desired. If the 'advanced' logic is in the VM, advanced controls must be shown for all views or no views; you can't show them for one and hide them for another. This, IMO, breaks the principles of having a VM in the first place.
(Thanks to all who posted here; it's been helpful!)

Proper way to display a view using MVVM

I am brand new to both wpf and MVVM. I have a Mainwindow that has two views left side has a usercontrol with a listbox and the list box has a edit button inside of it. On the right I have another view that contains all my controls for viewing and editing the record. I can select an item in the list box and edit my record since using binding it automatically populates by the selectedItem object. What I want to do is when the user hits the edit button show the view on the right if they hit another button contained in the list box then show that view on the right and close the previous view. I think I am missing a big concept here since most of the examples are to simplistic and just show one view. I really dont want to have to do it in the code behind. I have looked at John smiths tab and would like to do something similur without the tabs though. Any help would be greatly appreciated.
It sounds like both views need to share the same context (ie ViewModel) then they will stay in synch automaticall by the magic of dependency properties...
I would probably try setting it up so that clicking either button (View or Edit) sets the DataContext of the right frame. The RightFrame View gets displayed using DataTemplates based on the DataContext.
So your xaml would be something like this:
<DataTemplate DataType={x:Type MyEditingViewModel}>
<!-- Editing Object View -->
</DataTemplate>
<DataTemplate DataType={x:Type MyViewingViewModel}>
<!-- Viewing Object View -->
</DataTemplate>
and your button click events would be something like this:
private void EditButton_Click(object sender, EventArgs e)
{
RightFrame.DataContext = new MyEditingViewModel((sender as Button).DataContext)
}
private void ViewButton_Click(object sender, EventArgs e)
{
RightFrame.DataContext = new MyViewingViewModel((sender as Button).DataContext)
}
So basically, what you're trying to do is have your view model decide which view it should be presented in, and make that decision in response to user choice.
How I do this sort of thing:
My view model exposes a view style property, which is an enumerable. In the view for the view model (usually a user control), I implement a DockPanel to contain each style of view. Each DockPanel is assigned a style, and the styles are defined like this:
<Style x:Key="Style_View1" TargetType="DockPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ViewStyle}" Value="View1">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="Style_View2" TargetType="DockPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ViewStyle}" Value="View2">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
(Obviously you don't need to use a DockPanel if a Grid or StackPanel is more appropriate for your scenario. And you can implement a different user control for each style of view if you want to keep your code nicely segmented.)
So as long as the value of ViewStyle is one that there's a corresponding style for, that view style will be visible. Since all of the styles set Visibility to Collapsed by default, there will only ever be at most one view style visible.
There are lots of ways of selecting the view style - create a command for each one and bind it to buttons, create a group of radio buttons and use a value converter, set the view style in response to other properties in the view model - whatever works.

Resources