Adding custom style in WPF - c#

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.

Related

Metro Flyouts with MVVM

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

Custom WPF context menu with text on top

I created a custom context menu template which looks like this:
<Style TargetType="{x:Type ContextMenu}">
<Setter Property="Background" Value="{StaticResource menuBorderBrush}"/>
<Setter Property="Foreground" Value="{StaticResource menuForegroundBrush}"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContextMenu}">
<Border x:Name="Border" Background="{StaticResource menuBackgroundBrush}" BorderThickness="5" BorderBrush="{StaticResource menuBackgroundBrush}">
<StackPanel>
<TextBlock TextAlignment="Center" Padding="5,5,5,10">
You are not logged in.
</TextBlock>
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now i'd like to be able to change the "You are not logged in." text programmatically. I tried to start by creating a custom control class which inherits from ContextMenu and change the x:Type in the XAML to this class but then the Properties (like Background, Foreground, ...) are unknown.
How can i do this without implementing a new ContextMenu from scratch?
You can add to the TextBlock Binding with TemplatedParent for this.
<TextBlock TextAlignment="Center" Padding="5,5,5,10"
Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Tag}" />
And add the text to the ContextMenu Tag property, like that.
<Grid.ContextMenu>
<ContextMenu Tag="Test menuItem text" />
</Grid.ContextMenu>
Update
There are some tricks to pass many properties. For example you can bind ContextMenu to viewModel with many properties. The view Model should have realization INotifyPropertyChanged behaviour. So you can write something like this, but with many properties.
public class OwnObject : INotifyPropertyChanged
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged( "Text" ); }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void NotifyPropertyChanged( String info )
{
if ( PropertyChanged != null )
{
PropertyChanged( this, new PropertyChangedEventArgs( info ) );
}
}
}
And pass this viewModel object through the Tag property.
<Grid.ContextMenu>
<ContextMenu Tag="{Binding Object}" />
</Grid.ContextMenu>
And use it in the menu Style through the Tag.Text.
<TextBlock TextAlignment="Center" Padding="5,5,5,10"
Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Tag.Text}" />

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()));

WPF ComboBox selection change after switching tabs

I made a project based on nested tabs.
the nested tabs are different instance of the same viemModel and the same UI.
when I switch between the tabs he comboboxes present in the tabs chenge thei selection depending on the tab that is loosing focus.
I add both the viewmodels and the view of my test project.
thank you in advance for your help
main window
<Window.Resources>
<DataTemplate DataType="{x:Type local:IntermediateViewModel}">
<local:IntermediateView />
</DataTemplate>
<DataTemplate x:Key="HeaderedTabItemTemplate">
<Grid>
<ContentPresenter
Content="{Binding Path=Header, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center" >
</ContentPresenter>
</Grid>
</DataTemplate>
<Style x:Key="SimpleTabItemStyle" TargetType="TabItem">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border Name="Border" BorderThickness="1" BorderBrush="#555959">
<ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center"
ContentSource="Header" Margin="12,2,12,2" RecognizesAccessKey="True" Height ="40" MinWidth ="90"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#555959" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="DefaultTabControlTemplate">
<TabControl IsSynchronizedWithCurrentItem="True"
BorderThickness="0"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource HeaderedTabItemTemplate}"
ItemContainerStyle="{StaticResource SimpleTabItemStyle}"
SelectionChanged="TabControl_SelectionChanged"
/>
</DataTemplate>
<!---->
</Window.Resources>
<Grid MinHeight="200" MinWidth="300">
<Grid.RowDefinitions>
<RowDefinition Height="260*" />
<RowDefinition Height="51*" />
</Grid.RowDefinitions>
<Border >
<ContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{DynamicResource DefaultTabControlTemplate}"
/>
</Border>
<Button Grid.Row="1" Content="Add" Command="{Binding AddCommand}"/>
</Grid>
view model (create a different istance each time)
class MainWindowViewModel : WorkspacesViewModel<IntermediateViewModel>
{
public MainWindowViewModel()
{
this.WorkspacesView.CurrentChanged += new EventHandler(WorkspacesView_CurrentChanged);
}
void WorkspacesView_CurrentChanged(object sender, EventArgs e)
{
}
RelayCommand myVar = null;
public ICommand AddCommand
{
get
{
return myVar ?? (myVar = new RelayCommand(param =>
{
SetWindow(new IntermediateViewModel("AA" + this.Workspaces.Count) );
}));
}
}
first level tab
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:ClassViewModel}">
<local:ClassView />
</DataTemplate>
</UserControl.Resources>
<Border>
<ContentControl Content="{Binding Path=CurrentWorkspace, Mode=OneWay}" Loaded="ContentControl_Loaded" DataContextChanged="ContentControl_DataContextChanged" IsVisibleChanged="ContentControl_IsVisibleChanged" LayoutUpdated="ContentControl_LayoutUpdated" TargetUpdated="ContentControl_TargetUpdated" Unloaded="ContentControl_Unloaded" />
</Border>
first level viewmodel
class IntermediateViewModel : WorkspacesViewModel
{
public string Header { get; set; }
public IntermediateViewModel(string header)
{
Header = header;
SetWindow(new ClassViewModel(header));
}
}
nested tab
<UserControl.Resources>
<CollectionViewSource x:Key="StatusView" Source="{Binding Path=StatusList}"/>
</UserControl.Resources>
<Grid>
<ComboBox Name="_spl2Status" ItemsSource="{Binding Source={StaticResource StatusView}}"
SelectedValue="{Binding Path=MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="FL_TYPE"
DisplayMemberPath="ID_TYPE" Margin="76,12,0,0" Height="40" VerticalAlignment="Top" HorizontalAlignment="Left" Width="146"
DataContextChanged="_spl2Status_DataContextChanged"
IsVisibleChanged="_spl2Status_IsVisibleChanged"
Loaded="_spl2Status_Loaded"
SelectionChanged="_spl2Status_SelectionChanged"
>
</ComboBox>
</Grid>
nested tab view model
public enum myTypes
{
tipo0 = 0,
tipo1 = 1,
tipo2 = 2,
}
class ClassViewModel : WorkspaceViewModel
{
public ClassViewModel(string name)
{
Name = name;
}
public string Name { get; set; }
private List<IntEnumType> _statusList = null;
public List<IntEnumType> StatusList
{
get
{
if (_statusList == null)
_statusList = new List<IntEnumType>()
{
new IntEnumType((int)myTypes.tipo0, myTypes.tipo0.ToString()),
new IntEnumType((int)myTypes.tipo1, myTypes.tipo1.ToString()),
new IntEnumType((int)myTypes.tipo2, myTypes.tipo2.ToString()),
};
return _statusList;
}
}
private int myVar = 1;
public int MyProperty
{
get
{
return myVar;
}
set
{
if (myVar != value)
{
myVar = value;
OnPropertyChanged(() => MyProperty);
}
}
}
}
public class TabItemStyleSelector : StyleSelector
{
public Style MainTabItem { get; set; }
public Style ChildrenTabItem { get; set; }
public Style SpecificationTabItem { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
//if (item is IHome)
// return MainTabItem;
//else if (item is SpecificationItemViewModel)
// return SpecificationTabItem;
//else
return ChildrenTabItem;
}
}
The code is a little hard to completely follow, but I'm guessing that the issue is that there is only one instance of your ClassViewModel and it is where the selection for the combo box is stored {Binding Path=MyProperty, so whatever is stored in MyProperty will be reflected in all instances of the combo box regardless of where they live.
Well this is a bit late, but as I'm facing the same issue, I want to share my analysis.
When you change your tabs, you change the DataContext of the current Tab to your other ViewModel and hence also the ItemsSource of your ComboBox.
In case your previously selected Item (SelectedItem) is not contained within the new ItemsSource, the ComboBox fires a SelectionChanged-Event and therefore sets the SelectedIndex to -1.
Altough this default behaviour of the ComboBox might make sense, it's very annoying in many cases.
We've derived an own class from ComboBox, handling that. But it's not very satisfying as you loose some default behaviour you most probably need.
The problem is in your loaded event handlers.
When you switch tabs your unloading one tab and loading a new one.
I suspect your changing MyComboBox.SelectedIndex in _spl2Status_Loaded.

Different DataTemplate depending on the enumeration value of a property

I want something similar to what was asked here - but, I need the templates to depend on the value of a property, which is an enum.
The class looks simlar to this:
class ResultBlock
{
public string Name { get; set; }
public BlockType Type { get; set; }
public IList<ResultBlock> ChildBlocks { get; private set; }
}
Where BlockType has three different values, BLOCK, FILE, FOLDER - Now, I want to create a data template to present differently, depending on what value ResultBlock.Type has in the current object.
I tried doing this with DataType=, but that didn't work, obviously. I'm sure there is some way to do this very easily in XAML only.
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type docom:ResultBlock}" ItemsSource="{Binding ChildBlocks}">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type docom:BlockType.BLOCK}">
<TextBlock Text="BLOCK:{Binding Name}" />
</DataTemplate>
</StackPanel.Resources>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
You can trigger on the property, e.g.:
<HierarchicalDataTemplate DataType="{x:Type docom:ResultBlock}"
ItemsSource="{Binding ChildBlocks}">
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding BlockType}" Value="BLOCK">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- Data template for BLOCK blocks -->
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!-- More triggers -->
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</HierarchicalDataTemplate>
Yes, it's verbose. (You can define the templates for the different types as keyed resources and then reference them in the Setters though)
<Window.Resources>
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>
<Grid>
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"/>
</Grid>
public class TaskListDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is Task)
{
Task taskitem = item as Task;
if (taskitem.Priority == 1)
return
element.FindResource("importantTaskTemplate") as DataTemplate;
else
return
element.FindResource("myTaskTemplate") as DataTemplate;
}
return null;
}
}
This is implemented for ListBox but the idea can be same for DataGrid/TreeView .I hope this will help.
one way is to use a TemplateSelector and within the selector do what you want depending on your BlockType.
or you create wrapperviewmodels like:
public class ResultBlockBlock{}
public class ResultBlockFile{}
public class ResultBlockFolder{}
then you can go the DataTemplate DataType way

Categories