J_PT J_PT - 2 months ago 22
C# Question

WPF and Metro Binding

Perhaps you could help with this, I already tried several different ways but I m unable to achieve the desired results...

Using MahApps, I want to use ComboBoxes to change the applied theme to my WPF Window.

I used some of the code from MahApps Demo and adaped to my Project/Solution. I am able to load the Theme Names and the Accent Names to the ComboBoxes in my project, but now, when I change the selection in these comboboxes I want to call the command that exists in "AccentColorMenuData Class" using code behing in "SelectionChanged" events.

So My BaseModel code is:

public abstract class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
Debug.Write($"--- CLASS ViewModel Called OnPropertyChanged for object: {propertyName} -------- \n");
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}


My ViewModel is:

namespace WPFApplication.Ui
{
public class AccentColorMenuData
{
public string Name { get; set; }
public Brush BorderColorBrush { get; set; }
public Brush ColorBrush { get; set; }

private ICommand changeAccentCommand;

public ICommand ChangeAccentCommand
{
get { return this.changeAccentCommand ??
(changeAccentCommand = new SimpleCommand { CanExecuteDelegate = x => true, ExecuteDelegate = x => this.DoChangeTheme(x) }); }
}

protected virtual void DoChangeTheme(object sender)
{
var theme = ThemeManager.DetectAppStyle(Application.Current);
var accent = ThemeManager.GetAccent(this.Name);
ThemeManager.ChangeAppStyle(Application.Current, accent, theme.Item1);
}
}

public class AppThemeMenuData : AccentColorMenuData
{
protected override void DoChangeTheme(object sender)
{
var theme = ThemeManager.DetectAppStyle(Application.Current);
var appTheme = ThemeManager.GetAppTheme(this.Name);
ThemeManager.ChangeAppStyle(Application.Current, theme.Item2, appTheme);
}
}

public class RemoteSystemsViewModel : ViewModel
{
public List<AccentColorMenuData> AccentColors { get; set; }
public List<AppThemeMenuData> AppThemes { get; set; }

public RemoteSystemsViewModel()
{
// create accent color menu items for the demo
this.AccentColors = ThemeManager.Accents
.Select(a => new AccentColorMenuData() { Name = a.Name, ColorBrush = a.Resources["AccentColorBrush"] as Brush })
.ToList();

// create metro theme color menu items for the demo
this.AppThemes = ThemeManager.AppThemes
.Select(a => new AppThemeMenuData() { Name = a.Name, BorderColorBrush = a.Resources["BlackColorBrush"] as Brush, ColorBrush = a.Resources["WhiteColorBrush"] as Brush })
.ToList();
}
}
}


My Control s Code behind (Note I already have an event function for both Comboboxes)

public partial class ConfigurationView : UserControl
{
private readonly RemoteSystemsViewModel _viewModel;

public RemoteSystemsView()
{
_viewModel = new RemoteSystemsViewModel();
InitializeComponent();
DataContext = _viewModel;
}

void SelectionChanged(object sender, SelectionChangedEventArgs e)
{

}
}


And finally my XAML for the comboboxes inside the control

<

ComboBox x:Name="AccentColorsComboBox"
ItemsSource="{Binding AccentColors, Mode=OneWay}"
SelectedValuePath="Name"
DisplayMemberPath="Name"
SelectionChanged="SelectionChanged"
/>
<ComboBox x:Name="AppThemesComboBox"
ItemsSource="{Binding AppThemes, Mode=OneWay}"
SelectedValuePath="Name"
DisplayMemberPath="Name"
SelectionChanged="SelectionChanged"
>

</ComboBox>


AS I said, the Comboboxes are populated Ok with the correct information , but now, when I change 1 or the other I want to modified the applied theme.

I already tried many different things to achieve this but with no success, perhaps you can give a "hand"... :)

Thank you.

Update: Using Dustin Suggestion

void SelectionChanged(object sender, SelectionChangedEventArgs e)
{

string AccentName = this.AccentColorsComboBox.SelectedValue as string;
string ThemeName = this.AppThemesComboBox.SelectedValue as string;

if (!string.IsNullOrEmpty(AccentName) && !string.IsNullOrEmpty(ThemeName))
{
//Debug.Write($"{AccentName} - {ThemeName}\n");
ThemeManager.ChangeAppStyle(Application.Current, ThemeManager.GetAccent(AccentName), ThemeManager.GetAppTheme(ThemeName));
}
}


I meanwhile i also did:

void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox control = (ComboBox)sender;
String name = control.Name;
int index = -1;
index = control.SelectedIndex;
if (index > -1)
{
var vm = this.DataContext as RemoteSystemsViewModel;

if (name == "AccentColorsComboBox")
{
//Call command from viewmodel
if ((vm != null) && (vm.AccentColors[index].ChangeAccentCommand.CanExecute(null)))
vm.AccentColors[index].ChangeAccentCommand.Execute(null);
}
else
{
//Call command from viewmodel
if ((vm != null) && (vm.AppThemes[index].ChangeAccentCommand.CanExecute(null)))
vm.AppThemes[index].ChangeAccentCommand.Execute(null);
}
}

}


Now My problem with both approaches is when I try to change the theme 2 consecutive times, it only changes the theme if I change once then gogo back and make another selection and change it again... The Accents change OK at firs try.

Any Ideas?

UPDATE/SOLUTION IN C# (THANK YOU DUSTIN FOR THE HELP)

For XAML and CONTROLS the Code is the same as Dustin posted in his post.

For C# where it goes:

New Object:

public class ApplicationAccentColor
{
public string Name { get; set; }
public SolidColorBrush ColorBrush { get; set; }
}


VIEWMODEL + CONSTRUCTOR:

public class SystemsViewModel : ViewModel
{
/// Open Project Settings and select Settings, then add the following values:
/// Name= ApplicationThemeName Type = String Scope= User Value = BaseDark
/// Name= ApplicationAccentName Type = String Scope= User Value = Blue
/// </summary>

public IEnumerable<AppTheme> AppThemes { get; set; }
public IEnumerable<ApplicationAccentColor> AccentColors { get; set; }

public AppTheme SelectedTheme
{
/// Project Global Settings variable
/// Name= ApplicationThemeName Type = String Scope= User Value = BaseDark
/// Name= ApplicationAccentName Type = String Scope= User Value = Blue
get
{
/// Get default Values from global varable settings
return ThemeManager.GetAppTheme(Settings.Default.ApplicationThemeName);
}
set
{
/// Get default Values from global varable settings, and check if they are the same
if (object.ReferenceEquals(ThemeManager.GetAppTheme(Settings.Default.ApplicationThemeName), value))
return;

/// Save the new value to global settings variable
Settings.Default.ApplicationThemeName = value.Name;
Settings.Default.Save();

/// Apply the Theme
ThemeManager.ChangeAppStyle(Application.Current, ThemeManager.GetAccent(SelectedAccent.Name), ThemeManager.GetAppTheme(value.Name));
}
}

public ApplicationAccentColor SelectedAccent
{
/// Project Global Settings variable
/// Name= ApplicationThemeName Type = String Scope= User Value = BaseDark
/// Name= ApplicationAccentName Type = String Scope= User Value = Blue
get
{
//foreach (ApplicationAccentColor acc in AccentColors)
//{
// if (acc.Name == Settings.Default.ApplicationAccentName)
// {
// return acc;
// }
//}
//return new ApplicationAccentColor();

/// Get default Values from global varable settings
foreach (ApplicationAccentColor acc in from acc1 in AccentColors where acc1.Name == Settings.Default.ApplicationAccentName select acc1)
{
return acc;
}
return new ApplicationAccentColor();
}
set
{
/// Get default Values from global varable settings, and check if they are the same
if (object.ReferenceEquals(ThemeManager.GetAccent(Settings.Default.ApplicationAccentName).Name, value.Name))
return;

/// Save the new value to global settings variable
Settings.Default.ApplicationAccentName = value.Name;
Settings.Default.Save();

/// Apply the Theme
ThemeManager.ChangeAppStyle(Application.Current, ThemeManager.GetAccent(value.Name), ThemeManager.GetAppTheme(SelectedTheme.Name));
}
}


public SystemsViewModel()
{
AppThemes = ThemeManager.AppThemes;
AccentColors = ThemeManager.Accents.Select(a => new ApplicationAccentColor
{
Name = a.Name,
ColorBrush = (SolidColorBrush)a.Resources["AccentColorBrush"]
}).ToList();


}

}


APP.cs (To Load Latest Settings at start UP)

//Loading Values from Global Variables set in Application Settings
ThemeManager.ChangeAppStyle(Application.Current,
ThemeManager.GetAccent(Settings.Default.ApplicationAccentName.ToString()),
ThemeManager.GetAppTheme(Settings.Default.ApplicationThemeName.ToString()));


Thank you Dustin for your Help :)

Answer

This is how you change the theme/accent

ThemeManager.ChangeAppStyle(Windows.Application.Current, ThemeManager.GetAccent(AccentName), ThemeManager.GetAppTheme(ThemeName))

You are already using that in DoChangeTheme of AccentColorMenuData and AppThemeMenuData : AccentColorMenuData

UPDATE/EDIT: Here is how I implemented this same type of functionality. It's in VB but you can easily convert it if need be. Mine is in a flyout on the main window as part of settings:

Requires that you create Settings in the project that way this color info persists through sessions. You can do that in VS b opening My Project and selecting Settings. You add the following settings:

  • Name= ApplicationThemeName Type= String Scope= User Value=BaseDark

  • Name= ApplicationAccentName Type=String Scope= User Value=Blue

Of course you can have your defaults be whatever.

New Object:

Public Class ApplicationAccentColor
    Public Property Name As String
    Public Property ColorBrush As SolidColorBrush
End Class

In your viewModel------------------------

Props:

Public Property AppThemes As New List(Of AppTheme)

Public Property AccentColors As New List(Of ApplicationAccentColor)

Public Property SelectedTheme As AppTheme
    Get
        Return ThemeManager.GetAppTheme(MySettings.Default.ApplicationThemeName)
    End Get
    Set
        If ThemeManager.GetAppTheme(MySettings.Default.ApplicationThemeName) Is Value Then Exit Property
        MySettings.Default.ApplicationThemeName = value.Name
        MySettings.Default.Save()
        ThemeManager.ChangeAppStyle(Windows.Application.Current, ThemeManager.GetAccent(SelectedAccent.Name), ThemeManager.GetAppTheme(value.Name))
    End Set
End Property

Public Property SelectedAccent As ApplicationAccentColor
    Get
        For Each acc As ApplicationAccentColor In From acc1 In AccentColors Where acc1.Name = MySettings.Default.ApplicationAccentName
            Return acc
        Next
        Return New ApplicationAccentColor()
    End Get
    Set
        If ThemeManager.GetAccent(MySettings.Default.ApplicationAccentName).Name is value.Name Then Exit Property
        MySettings.Default.ApplicationAccentName = value.Name
        MySettings.Default.Save()
        ThemeManager.ChangeAppStyle(Windows.Application.Current, ThemeManager.GetAccent(value.Name), ThemeManager.GetAppTheme(SelectedTheme.Name))
    End Set
End Property

Constructor:

AppThemes = ThemeManager.AppThemes
AccentColors = ThemeManager.Accents.Select(Function(a) New ApplicationAccentColor With {.Name = a.Name, .ColorBrush = a.Resources("AccentColorBrush")}).ToList()

XAML:

Control resource:

<DataTemplate x:Key="AppThemeItemsTemplate">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Ellipse Grid.Column="0" Width="20" Height="20" Fill="{Binding Resources[WhiteColorBrush]}" Stroke="{Binding Resources[BlackColorBrush]}"/>
        <Label Grid.Column="1" Content="{Binding Name}"/>
    </Grid>
</DataTemplate>

<DataTemplate x:Key="AccentColorItemsTemplate">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Ellipse Grid.Column="0" Width="20" Height="20" Fill="{Binding ColorBrush}"/>
        <Label Grid.Column="1" Content="{Binding Name}"/>
    </Grid>
</DataTemplate>

Actual controls:

        <StackPanel Margin="10">
            <Label Content="Theme"/>
            <ComboBox Width="125" HorizontalContentAlignment="Left" HorizontalAlignment="Left" ItemTemplate="{StaticResource AppThemeItemsTemplate}" ItemsSource="{Binding AppThemes}" SelectedItem="{Binding SelectedTheme}"/>
            <Label Content="Accent"/>
            <ComboBox Width="125" HorizontalContentAlignment="Left" HorizontalAlignment="Left" ItemTemplate="{StaticResource AccentColorItemsTemplate}" ItemsSource="{Binding AccentColors}" SelectedItem="{Binding SelectedAccent}"/>
            <Button Style="{StaticResource AccentedSquareButtonStyle}" Command="{Binding RefreshDirectoriesFiles}" Margin="0,20,0,0" Content="Refresh Directories/Files"/>
        </StackPanel>
Comments