WPF button styling depending on active view - wpf

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

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>

Set property after command

I have a StatusBar below the screen in SL4 (using PRISM), just a very simple Telerik RadDockPanel.
I also have a menu (Telerik RibbonView with RadRibbonGroup and RadRibbonToggleButton). When the toggle button is pressed, I want to set the text to 'ON' and 'OFF', and I want to hide the status bar, but... only in XAML (not using code behind).
I believe this is a common SL/WPF coding practice... but how ?
Have to use EventTrigger (check example bellow on page by link that I provided) and ObjectAnimationUsingKeyFrames to change properties that aren't animated (Text, Visibility so on).
Check good example in other answer on so.
You can specify a DataTrigger in your window like this -
<StatusBar.Style>
<Style>
<Style.Triggers>
<DataTrigger
Binding="{Binding ElementName=MyRadRibbonToggleButton, Path=IsChecked}"
Value="True">
<Setter Property="Grid.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBar.Style>
In case you can't use ElementName binding then you can use a property in your ViewModel(corresponding to RadRibbonToggleButton state). Similar Trigger can be created for a TextBlock/Label to show On/Off text.
This is how I implement this kind of functionality in WPF/MVVM applications;
You may have to apply some hack to make this work with telerik controls.

Let one control be enabled/disabled based on another control

I have two DataGrid's that I want do have enabled/disabled based on whether precisely 1 element is selected in another DataGrid. What is the simplest way to accomplish this dependency control in WPF?
You could use a trigger:
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItems.Count,
ElementName=datagrid1}"
Value="1">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
You could:
Create an IValueConverter, perhaps it is called NotEqualToOneBooleanConverter
Bind IsEnabled from one DataGrid to the SelectedItems.Count on the other
Set the Converter on this Binding to be the NotEqualToOneBooleanConverter
This approach is nice since once your converter is created, it can be applied throughout your XAML and to any type and any property (not just DataGrid or SelectedItems.Count). To make it even more flexible, you could have a more generic version of this converter that could compare any two values specified directly from XAML (one from the Binding and one specified as property on the Converter).
The downside to this approach - it's XAML only, and difficult to test especially if what you are trying to achieve is a business requirement and not just a graphical effect.
Hope this helps!
This is my quick hack:
tablesControl.SelectionChanged += (sender, sce) =>
{
var c = tablesControl.SelectedItems.Count;
var orderingPossible = c == 1;
itemsControl.IsEnabled = orderingPossible;
};
In the first Grid have an event or Command that is fired when you click on that cell, in this event you need to have some bool property you can set to false, then bind the Enabled property to this bool. If you are using MVVM this will be very easy, have a look at this to see how - http://www.youtube.com/watch?v=tKfpvs7ZIyo

The MVVM way of presenting controls dynamically

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.

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