Metro Flyouts with MVVM - c#

I'm trying to use the Flyouts from MahApps.Metro in my application. So I added this part to my MainWindow.xaml:
<controls:MetroWindow.Flyouts>
<controls:FlyoutsControl ItemsSource="{Binding Flyouts}">
<controls:FlyoutsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:SettingsViewModel}">
<view:SettingsFlyout/>
</DataTemplate>
</controls:FlyoutsControl.ItemTemplate>
</controls:FlyoutsControl>
</controls:MetroWindow.Flyouts>
The ItemTemplate will contain the mappings from my viewmodels to the views. Flyouts is an ObservableCollection<IFlyoutViewModel> and currently only contains my SettingsViewModel.
The IFlyoutViewModel definition:
using System.ComponentModel;
namespace MyApplication.ViewModel
{
internal interface IFlyoutViewModel : INotifyPropertyChanged
{
bool Visible { get; set; }
}
}
And how I use the Visible-property:
<controls:Flyout x:Class="MyApplication.View.SettingsFlyout"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
Header="Settings"
Position="Right"
IsOpen="{Binding Visible}"
Width="300">
...
</controls:Flyout>
So now I set the Visible-property of my SettingsViewModel, but the Flyout won't open. What am I doing wrong?
I just tried to assign IsOpen="true" hardcoded but this didn't work, too. So displaying the flyout with a datatemplate seems to be the problem...

I built it like described in the issue dicussion linked by Eldho, now it works. The key ist to define ItemContainerStyle and bind IsOpen there!
The new MainWindow.xaml:
<controls:MetroWindow.Flyouts>
<controls:FlyoutsControl ItemsSource="{Binding Flyouts}">
<controls:FlyoutsControl.Resources>
<view:FlyoutPositionConverter x:Key="FlyoutPositionConverter"/>
</controls:FlyoutsControl.Resources>
<controls:FlyoutsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:SettingsViewModel}">
<view:SettingsFlyout/>
</DataTemplate>
</controls:FlyoutsControl.ItemTemplate>
<controls:FlyoutsControl.ItemContainerStyle>
<Style BasedOn="{StaticResource {x:Type controls:Flyout}}"
TargetType="{x:Type controls:Flyout}">
<Setter Property="Header"
Value="{Binding Header}" />
<Setter Property="IsOpen"
Value="{Binding Visible}" />
<Setter Property="Position"
Value="{Binding Position, Converter={StaticResource FlyoutPositionConverter}}" />
<Setter Property="IsModal"
Value="{Binding IsModal}" />
<Setter Property="Theme" Value="Accent" />
</Style>
</controls:FlyoutsControl.ItemContainerStyle>
</controls:FlyoutsControl>
</controls:MetroWindow.Flyouts>
The new IFlyoutViewModel:
using System.ComponentModel;
namespace MyApplication.ViewModel
{
internal interface IFlyoutViewModel : INotifyPropertyChanged
{
string Header { get; }
bool Visible { get; set; }
Position Position { get; set; }
bool IsModal { get; set; }
}
public enum Position
{
Top,
Left,
Right,
Bottom
}
}
The FlyoutPositionConverter is just a mapper between my position enum and the MahApps.Metro.Controls.Position because I didn't want to use the real positon in my viewmodel interface.
Also the view now no longer needs to be a Flyout, it can be a normal usercontrol.

Your first solution also should work well if you call OnPropertyChanged on the Setter of your Visible-Property

Related

Adding custom style in WPF

I have created custom style as below
public class MenuStyle: StyleSelector
{
public override Style SelectStyle(object item, DependencyObject container)
{
// my code
}
}
I am using this style in xaml file as below.
I am using it as below.
Added namespace as below
xmlns:style="clr-namespace:MedicalStore.Styles"
Added resource as
<UserControl.Resources>
<style:MenuStyle x:Key="MenuStyle"></style:MenuStyle>
<Style TargetType="MenuItem" x:Key="SelectedMenuItem">
<Setter Property="Background" Value="White"></Setter>
</Style>
</UserControl.Resources>
and using it as below
<Menu DockPanel.Dock="Top" FontSize="22" Background="Green" HorizontalAlignment="Right" x:Name="MainMenu"
ItemsSource="{Binding Path=MenuItems}" DisplayMemberPath="Text"
ItemContainerStyleSelector="{Binding MenuStyle}">
</Menu>
but as I run my app, debugger never goes to MenuStyle class. What is the issue?
A good approach is to implement Style-properties in your MenuStyle-Class.
public class MenuStyle : StyleSelector
{
// Declare all the style you're going to need right here
public Style StyleMenuItem { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
// here you can check which type item is and return the corresponding style
if(item != null && typeof(item) == typeof(YourType))
{
return StyleMenuItem;
}
}
}
With that, you can do following in your XAML
<UserControl.Resources>
<Style TargetType="MenuItem" x:Key="SelectedMenuItemStyle">
<Setter Property="Background" Value="White"></Setter>
</Style>
<style:MenuStyle x:Key="MenuStyle" StyleMenuItem="{StaticResource SelectedMenuItemStyle}"/>
</UserControl.Resources>
Note that I changed the x:Key of your Style to make things more clear.
Next thing is in your Menu:
<Menu DockPanel.Dock="Top" FontSize="22" Background="Green" HorizontalAlignment="Right" x:Name="MainMenu"
ItemsSource="{Binding Path=MenuItems}" DisplayMemberPath="Text"
ItemContainerStyleSelector="{StaticResource MenuStyle}">
</Menu>
There, you have to use StaticResource instead of Binding.
This should be all.

How do I use a window-level databound style in a DataTemplate

In a WPF project, my viewmodel has some general properties that I bind to a style. I then would like to use that style in a DataTemplate where I bind a collection from my viewmodel.
The databound style works outside the DataTemplate as expected, but does not apply inside. When debugging I can see that it is looking for the general properties inside the collection objects, so my question is, how do I inside a DataTemplate get a hold of properties from the viewmodel. I imagine I have to use a RelativeSource binding, but I have not been able to get it working.
This quick app should show what I am trying to do:
MainWindow.xaml
<Window x:Class="StyleTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:StyleTest"
mc:Ignorable="d"
Title="Test"
SizeToContent="WidthAndHeight">
<Window.Resources>
<Style TargetType="TextBlock" x:Key="Header">
<Setter Property="FontSize" Value="{Binding FontSize}" />
<Setter Property="Foreground" Value="{Binding Foreground}" />
</Style>
<DataTemplate x:Key="UserTemplate">
<StackPanel>
<TextBlock Style="{StaticResource Header}" Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Margin="20">
<StackPanel>
<ItemsControl Name="Itemscontrol" ItemsSource="{Binding Users}" ItemTemplate="{StaticResource UserTemplate}" />
<TextBlock Style="{StaticResource Header}">Style this.</TextBlock>
</StackPanel>
</Grid>
</Window>
MainWindow.cs
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
namespace StyleTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Model m = new Model {
FontSize = 28,
Foreground = new SolidColorBrush(Colors.Orange),
Users = new List<User>() };
m.Users.Add(new User() { Name = "Mambo No. 1" });
m.Users.Add(new User() { Name = "Right Hand Rob" });
m.Users.Add(new User() { Name = "Perry Junior" });
this.DataContext = m;
}
}
public class Model
{
private int fontSize;
public int FontSize { get => fontSize; set => fontSize = value; }
private SolidColorBrush foreground;
public SolidColorBrush Foreground { get => foreground; set => foreground = value; }
private List<User> users;
public List<User> Users { get => users; set => users = value; }
}
public class User
{
public string Name { get; set; }
}
}
I think you want something like this:
<Style TargetType="TextBlock" x:Key="Header">
<Setter Property="FontSize" Value="{Binding Path=DataContext.FontSize, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
<Setter Property="Foreground" Value="{Binding Path=DataContext.Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
</Style>

Load user control in tab item

i am trying to load user control in tab item dynamically but i am unable to do so i am using below code .I have visited various posts also but i have got the below way please let me know where i am wrong:
User control:
<UserControl x:Class="WpfApplication1.UserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Button Content="welcome" Height="20" Width="70"></Button>
</Grid>
</UserControl>
ViewMOdel:
namespace WpfApplication1
{
class Usercontrol2ViewModel
{
public ObservableCollection<TabItem> Tabs { get; set; }
public Usercontrol2ViewModel()
{
Tabs = new ObservableCollection<TabItem>();
//Tabs.Add(new TabItem { Header = "Overview", Content = new OverviewViewModel() }); How to load a usercontrol here if it's in the ItemCollection?
Tabs.Add(new TabItem { Header = "Overview", Content = new UserControl2() });
}
}
public class TabItem
{
public string Header { get; set; }
public object Content { get; set; } // object to allow all sort of items??
}
}
MainPage:
<TabControl x:Name="MyTabControl"
ItemsSource="{Binding Tabs}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type local:Usercontrol2ViewModel}">
<local:UserControl2></local:UserControl2>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
I found the issue below is the solution of code patch that needs to be correct above posted code
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="Content" Value="{Binding Content}"></Setter>
</Style>

Disable a subview depending viewmodel bool property (MVVM architecture)

I have a view that contains multiple subview (UserControl).
<StackPanel>
<local:SubViewGPS/>
<local:SubViewText/>
</StackPanel>
This view binds to a ViewModel and I would like to load or not load subview depending on bool properties of my ViewModel
private bool isGPSCompatible;
public bool IsGPSCompatible {
get { return isGPSCompatible; }
set {
if (isGPSCompatible != value) {
isGPSCompatible = value;
NotifyPropertyChanged();
}
}
}
private bool isTextCompatible;
public bool IsTextCompatible {
get { return isTextCompatible; }
set {
if (isTextCompatible != value) {
isTextCompatible = value;
NotifyPropertyChanged();
}
}
}
I actually don't want to "Disable" or change the "Visibility" but really avoid to load the component if the property is false. According this post: Different views / data template based on member variable the combination of DataTemplate and DataTrigger seems to be a way to reach the goal but I was wondering if it exist something simpler. Thanks for your help
I finally used this solution:
<UserControl x:Class="RLinkClient.LocationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:client"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate x:Key="GPSLocationViewTemplate">
<local:GPSControl/>
</DataTemplate>
<DataTemplate x:Key="NoGPSViewTemplate">
<TextBlock Text="GPS Disabled" TextAlignment="Center" VerticalAlignment="Center" FontStyle="Italic" Opacity="0.1" FontWeight="Bold" FontSize="14"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource NoGPSViewTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding GPSCapable}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource GPSLocationViewTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</UserControl>
but Frank answer is totally acceptable if I could change the ViewModel structure.
As you're using MVVM, you could create separate sub-viewmodels for the different views and add them to some ObservableCollection, depending on their properties:
<!-- This would replace your StackPanel -->
<ItemsControl ItemsSource="{Binding Capabilities}">
<ItemsControl.Resources>
<DataTemplate DataType="localVm:GpsViewModel">
<local:SubViewGPS />
</DataTemplate>
<DataTemplate DataType="localVm:TextViewModel">
<local:SubViewText />
</DataTemplate>
<!-- ... -->
</ItemsControl.Resources>
</ItemsControl>
You would have an ObservableCollection in your view model, like this:
public ObservableCollection<ICapability> Capabilities { get; private set; }
and add sub-viewmodels implementing ICapability to this as needed.
Based on the condition, I suggest adding the view object in code instead of XAML. Only instantiate when needed to avoid unnecessary initialization routines. That is, don't instantiate the view before checking the condition:
if (IsGPSCompatible )
myStackPanel.Children.Add((new SubViewGPSView()));
if (IsTextCompatible )
myStackPanel.Children.Add((new SubViewText()));

Problems Binding MouseDoubleClick via M-V-MV Design Pattern

I have the following snippet of code in an .xaml file:
<TreeView MouseDoubleClick="TreeView_MouseDoubleClick" ItemsSource="{Binding MyList}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
As you can see, when you "MouseDoubleClick" on an item in the TreeView it will execute the code in the code behind...namely...
private void TreeView_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
try
{
MessageBox.Show(((TreeViewWithViewModelDemo.LoadOnDemand.HtmlFileViewModel)(((System.Windows.Controls.TreeView)(sender)).SelectedValue)).HtmlFileName);
}
catch
{
}
}
I'm trying to follow the Model-View-ViewModel Design Pattern and would like to move the implementation of this MouseDoubleClick event away from the View and into the ViewModel.
I understand that if I was using a command I would use {Binding Command="Select"} (or something similar that implements the ICommand interface) but I cannot seem to find the syntax for this particular issue since it is not a command button.
Can someone help me out?
Thanks
Here's a solution using Blend's interaction triggers.
<Page.DataContext>
<Samples:TreeViewDblClickViewModel/>
</Page.DataContext>
<Grid>
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<DataTemplate>
<ContentControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding DoubleClickCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock Text="{Binding Name}" Background="AliceBlue" Margin="2"/>
</ContentControl>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
View model
public class TreeViewDblClickViewModel
{
public TreeViewDblClickViewModel()
{
Items = new List<TreeViewDblClickItem>
{
new TreeViewDblClickItem{ Name = "One"},
new TreeViewDblClickItem{ Name = "Two"},
new TreeViewDblClickItem{ Name = "Thee"},
new TreeViewDblClickItem{ Name = "Four"},
};
}
public IList<TreeViewDblClickItem> Items { get; private set; }
}
public class TreeViewDblClickItem
{
public TreeViewDblClickItem()
{
DoubleClickCommand = new ActionCommand(DoubleClick);
}
public string Name { get; set; }
private void DoubleClick()
{
Debug.WriteLine("Double click");
}
public ICommand DoubleClickCommand { get; private set; }
}
Using MVVM doesn't mean there mustn't be any code in the code-behind file. It just means moving all the associated logic into the viewmodel. You could just implement necessary double-click method on the viewmodel, and call it from the code behind like this:
_viewModel.MouseDoubleClickOnTree();
Also, I'd recommend looking at this topic: MVVM C# WPF binding mouse double click
I recommend you to start using library for MVVM pattern like Prism etc. It solves general problems and you can spend your time doing business stuff and not reinvent the wheel.
I actually posted an answer to someone about this very topic a few days ago. Here is what I posted
Obviously that is for a listviewitem, not a treeviewitem but it will still work, with some minor changes.

Categories