WPF caliburn Screen and Tab control - c#

At the moment I having working tabs and they open and close properly. I'm trying to implement a Close All functionality and a Close all but this tab functionality, was wondering how abouts do I do that? The Tabs are Initialized in my ShellViewModel.
Current TabsView.xaml
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<StackPanel Name="Panel" Background="#88DDDDDD" SnapsToDevicePixels="True" Orientation="Horizontal" Margin="1 0" cal:Message.Attach="[Event MouseDown] = [Action Show($dataContext)]">
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center" TextAlignment="Right" FontWeight="Bold" >
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Close All Tabs" cal:Message.Attach="[Event Click] = [Action CloseTabs($this)]"/>
<MenuItem Header="Close All But This" cal:Message.Attach="[Event Click] = [Action CloseAllButThis]"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
<Button Background="Transparent" cal:Message.Attach="[Click] = [Close($this)]" BorderThickness="0" VerticalAlignment="Center">
<ContentControl ContentTemplate="{StaticResource Icons.CloseButtonSmall}" Background="#900" Width="10" Height="10" Margin="3" VerticalAlignment="Center"/>
</Button>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Panel" Property="Background" Value="#DDD"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="#093"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
TabsViewModel.cs
private readonly IEventAggregator _events;
public List<IScreen> Items { get; private set; }
public WorkTabsViewModel(IEventAggregator events)
{
_events = events;
_events.Subscribe(this);
}
public void Close(IScreen tab)
{
if (tab.DisplayName == "Settings")
{
var settingsViewModel = tab as SettingsViewModel;
if (settingsViewModel != null)
{
tab.TryClose();
}
}
else
{
tab.TryClose();
}
}
public void Show(IScreen screen)
{
_events.PublishOnUIThread(new ShowTabEvent(screen));
}
public void Handle(ScreenChangeEvent screenChangeEvent)
{
Items = screenChangeEvent.Tabs.Where(x => Array.IndexOf(HiddenTabs, x.GetType().Name) < 0).ToList();
NotifyOfPropertyChange(() => Items);
}

In your ViewModel you can do the following:
public class WorkTabsViewModelpublic extends Conductor<IScreen>.Collection.OneActive
{
// ....
void CloseAll() {
foreach (IScreen tab in Items)
{
tab.TryClose();
}
}
// ....
}
Then in your view you can add a button that will call that method when clicked
<Button x:Name="CloseAll">Close All Tabs</Button>
As long as your ViewModel is extends a Conductor<IScreen>.Collection.OneActive (or something similar), this will loop through all of your open tabs and try to close them.

Related

Change color when the button is active (WPF in MVVM architecture)

<Button Command="{Binding LanguageChangeCommand}"
CommandParameter="English" Content="English"
Background="{Binding Button0}" Width="80" Grid.Column="3" Grid.Row="3"
Margin="8,15,162,21"/>
<Button Command="{Binding LanguageChangeCommand}"
CommandParameter="China" Content="中国"
Background="{Binding Button1}" Width="80" Grid.Column="3" Grid.Row="3"
Margin="78,19,77,21" />
<Button Command="{Binding LanguageChangeCommand}"
CommandParameter="Tamil" Content="தமிழ்"
Background="{Binding Button2}" Width="80" Grid.Column="3" Grid.Row="3"
Margin="165,15,10,21" />
I want to change the color of button when An active button. Please help me
I want to change my button colors when one button clicked the color should be change as different from other buttons. and after clicking an another button the button clicked previously should be colored as normal an just clicked button should as mentioned.
You can easily achieve this using RadioButton grouping.
<Window.Resources>
<Style x:Key="GroupToggleStyle" TargetType="RadioButton"
BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Foreground" Value="Blue"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}"
Value="True">
<DataTrigger.Setters>
<Setter Property="Foreground" Value="Red"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<RadioButton GroupName="LanguageGroup" Command="{Binding LanguageChangeCommand}"
CommandParameter="English" Content="English" Width="80"
Style="{StaticResource GroupToggleStyle}"/>
<RadioButton GroupName="LanguageGroup" Command="{Binding LanguageChangeCommand}"
CommandParameter="China" Content="中国" Width="80"
Style="{StaticResource GroupToggleStyle}"/>
<RadioButton GroupName="LanguageGroup" Command="{Binding LanguageChangeCommand}"
CommandParameter="Tamil" Content="தமிழ்" Width="80"
Style="{StaticResource GroupToggleStyle}"/>
</StackPanel>
Output
You could try to refer to the following code.
MainWindow.xaml:
<StackPanel>
<Button Height="50" Width="100" Content="Button1" Command="{Binding LanguageChangeCommand}" CommandParameter="English">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Orange"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsButton1Active}" Value="True">
<Setter Property="Background" Value="LightSeaGreen" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Height="50" Width="100" Content="Button2" Command="{Binding LanguageChangeCommand}" CommandParameter="China">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Orange"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsButton2Active}" Value="True">
<Setter Property="Background" Value="LightSeaGreen" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
MainWindow.xaml.cs:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
namespace ButtonCommand
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext=new ViewModel();
}
}
public class ViewModel:INotifyPropertyChanged
{
public ViewModel()
{
languageChangeCommand = new UICommand(OnClick);
}
private UICommand languageChangeCommand;
public UICommand LanguageChangeCommand
{
get { return languageChangeCommand; }
}
private void OnClick(object parameter)
{
switch (parameter.ToString())
{
case "English":
IsButton1Active = true;
IsButton2Active = false;
break;
case "China":
IsButton2Active = true;
IsButton1Active = false;
break;
}
}
private bool isButton1Active;
private bool isButton2Active;
public bool IsButton1Active
{
get { return isButton1Active; }
set { isButton1Active = value; OnPropertyChanged(); }
}
public bool IsButton2Active
{
get { return isButton2Active; }
set { isButton2Active = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class UICommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<bool> _canExecute;
public UICommand(Action<object> onExecuteMethod, Func<bool> onCanExecuteMethod = null)
{
_execute = onExecuteMethod;
_canExecute = onCanExecuteMethod;
}
public bool IsCanExecute { get; set; }
public event EventHandler CanExecuteChanged
{
add { if (_canExecute != null) CommandManager.RequerySuggested += value; }
remove { if (_canExecute != null) CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
public bool CanExecute(object parameter)
{
IsCanExecute = (_canExecute == null || _canExecute());
return IsCanExecute;
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
}

How to Use Inheritance With MVVM in the Right Way

I am trying to change the CurrentCarViewModel's model Type based on the selected value in a ListBox. Currently, in the ListBox's SelectionChanged event I use a switch statement to create a new model with the correct type inside CurrentCarViewModel. I don't think it's the best solution, because I feel switching the type every time I want to operate inside the CarViewModel is not good.
This is my MainWindow.xaml:
public partial class MainWindow : Window
{
public CarViewModel CurrentCarViewModel { get; set; } = new CarViewModel();
public MainWindow()
{
InitializeComponent();
...
}
private void CarType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
switch ((sender as ListBox).SelectedIndex)
{
...
}
}
}
public enum CarType { ElectricCar, GasCar }
public class CarViewModel
{
public Car Model { get; set; }
public CarType CarType { get; set; }
}
public class Car { }
public class ElectricCar : Car { }
public class GasCar : Car { }
and here's the ListBox:
<ListBox SelectionChanged="CarType_SelectionChanged" Grid.Row="0">
<ListBoxItem Content="Electric Car"/>
<ListBoxItem Content="Gas Car"/>
</ListBox>
This operation is not simple, so I made it as a sample.
I made a sample of the source code you want. Please check it.
GitHub
Xaml
<ListBox ItemsSource="{Binding Cars}"
SelectedItem="{Binding CurrentCar}"/>
ViewModel
{
public class MainViewModel
{
private Car _currentCar;
private Car CurrentCar
{
get { return _currentCar; }
set { _currentCar = value; CarChanged(value); }
}
public List<Car> Cars { get; set; }
public MainViewModel()
{
Cars = new List<Car>
{
new Electric { Name = "ModelS", Brand = "TESLA", BatteryCharge = 100 },
new Gasoline { Name = "E-Class", Brand = "BENZ", Displacement = 2000 },
new Gasoline { Name = "5-Series", Brand = "BMW", Displacement = 2000 }
};
}
private void CarChanged(Car value)
{
if (value is Electric ele)
{
// Electric
Debug.WriteLine(ele.GetType().ToString());
}
else if (value is Gasoline gas)
{
// Gasolin
Debug.WriteLine(gas.GetType().ToString());
}
}
}
ListBoxItem Resource
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Foreground" Value="#FFFFFF"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Background="{TemplateBinding Background}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="COL_A"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="COL_B"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Viewbox Grid.Column="0" Width="16" Height="16" Margin="4 4 8 4" VerticalAlignment="Center">
<Path x:Name="path" Width="24" Height="24"/>
</Viewbox>
<TextBlock Grid.Column="1" Text="{Binding Name}" Margin="0 0 6 0" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBlock Grid.Column="2" Text="{Binding Brand}" Margin="0 0 6 0" VerticalAlignment="Center"/>
<TextBlock Grid.Column="3" Text="{Binding Type}" Margin="0 0 0 0" VerticalAlignment="Center" Foreground="#cccccc"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#FF37528B"/>
</Trigger>
<DataTrigger Binding="{Binding Type}" Value="{x:Type model:Electric}">
<Setter TargetName="path" Property="Data" Value="{StaticResource GEO.BATTERY}"/>
<Setter TargetName="path" Property="Fill" Value="#FDFD86"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="{x:Type model:Gasoline}">
<Setter TargetName="path" Property="Data" Value="{StaticResource GEO.GASOLINE}"/>
<Setter TargetName="path" Property="Fill" Value="#FFACBBCD"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Grid.IsSharedSizeScope" Value="True"/>
</Style>
Path
<Geometry x:Key="GEO.GASOLINE">M3,2H6C6.28,2 6.53,2.11 6.71,2.29L8.79,4.38L9.59,3.59C10,3.2 10.5,3 11,3H17C17.5,3 18,3.2 18.41,3.59L19.41,4.59C19.8,5 20,5.5 20,6V19A2,2 0 0,1 18,21H8A2,2 0 0,1 6,19V13L6,12V8C6,7.5 6.2,7 6.59,6.59L7.38,5.79L5.59,4H3V2M11,5V7H17V5H11M11.41,11L9.41,9H8V10.41L10,12.41V15.59L8,17.59V19H9.41L11.41,17H14.59L16.59,19H18V17.59L16,15.59V12.41L18,10.41V9H16.59L14.59,11H11.41M12,13H14V15H12V13Z</Geometry>
<Geometry x:Key="GEO.BATTERY">M16,15H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z</Geometry>

Button ContextMenu Image only shown at last item

I have a problem with a dynamic ContextMenu on a Button.
The Button is inside a DataGrid with the following Columndefinition in a ResourceDictionary:
<DataGridTemplateColumn Width="16" x:Key="ShowContextMenuColumn" x:Shared="False" KeyboardNavigation.IsTabStop="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Style="{StaticResource ShowContextMenuButtonStyle}">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding Data.ContextActionItems, Source={StaticResource proxy}}" DisplayMemberPath="DisplayText">
<ContextMenu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Icon">
<Setter.Value>
<Image Source="{Binding Image}"/>
</Setter.Value>
</Setter>
<Setter Property="Command" Value="{Binding Command}"/>
</Style>
</ContextMenu.Resources>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellStyle>
<Style TargetType="{markup:Type DataGridCell}" BasedOn="{StaticResource BaseDataGridCellStyle}">
<Setter Property="AutomationProperties.Name" Value="ContextMenu"/>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>
The ObservableCollection<DisplayListActionItem> for the ContextActionItems is defined in an abstract generic base-class which is inherited by several ViewModels.
The DisplayListActionItem-Class looks like:
public class DisplayListActionItem : NotifyBase
{
public string DisplayText
{
get { return Get<string>(); }
set { Set(value); }
}
public ICommand Command
{
get { return Get<ICommand>(); }
set { Set(value); }
}
public ImageSource Image
{
get { return Get<ImageSource>(); }
set { Set(value); }
}
}
Everything just works fine. The only problem is, that the Image is only shown at the last item of the ContextMenu. If I remove the last item, then the image of the new last item is shown. I don't understand what's the reason for this.
I've already checked the Debug-Output for Binding-Errors, but there are none.
Any ideas what could cause this problem?
Solved it by myself.
The solution was to modify the ContextMenu-ItemContainerStyle instead doing the needed stuff in the Resources of the ContextMenu.
The new xaml for the Button looks like:
<Button Style="{StaticResource ShowContextMenuButtonStyle}">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding Data.ContextActionItems, Source={StaticResource proxy}}" >
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Icon" Value="{Binding Image, Converter={converter:ImageSourceToImageConverter}}"/>
<Setter Property="Header" Value="{Binding DisplayText}"/>
<Setter Property="Command" Value="{Binding Command}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Button.ContextMenu>
</Button>

Item command cannot edit another item in the list

I have a sidebar (in a C# WPF program) that should display 4 "different" buttons (They are actually 2 different styles, which both have another style for the active state). The sidebar consists of an ItemsControl. I've now managed to create a list where the correct style is used based on an enum value (as shown below). Here's a small question: Can I do it this way, or should I rewrite it, and if so, how could something like this be built? Keywords or something that I have to look at are enough for me.
My real question now is: I have bound a command to every button, nothing complicated at first. The command now sets its own state to NormalActive for testing purposes. The 1st item in this list should be set from LiveActive to Live (so that you always see the currently selected item as you know it). And here's the problem: The button can set its own state, so when I click on button 3, the state of button 3 is set from Normal to NormalActive. But what doesn't happen is the change from LiveActive to Active from the 1st button. Even if I output the current state to the console before and after the change, it returns LiveActive for both. I also tried invoking the whole thing into the dispatcher if I'm not in the UI thread for some reason, it didn't work. So the button can set its own state, but not the one of another. But I don't get an error message or anything. Also the setter method of the property is called, it just doesn't change it. What could be the reason?
PluginListControl:
<Grid DataContext="{x:Static local:PluginListDesignModel.Instance}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:PluginListItemControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
PluginListItemControl:
<UserControl.Resources>
<DataTemplate x:Key="PluginTile" DataType="{x:Type local:PluginListItemViewModel}">
<Button Style="{StaticResource PluginTile}" Content="{Binding DataContext.Name, RelativeSource={RelativeSource AncestorType=ContentControl}}" Command="{Binding DataContext.SetStateCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
<DataTemplate x:Key="PluginActiveTile" DataType="{x:Type local:PluginListItemViewModel}">
<Button Style="{StaticResource PluginActiveTile}" Content="{Binding DataContext.Name, RelativeSource={RelativeSource AncestorType=ContentControl}}" Command="{Binding DataContext.SetStateCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
<DataTemplate x:Key="PluginLiveTile" DataType="{x:Type local:PluginListItemViewModel}">
<Button Style="{StaticResource PluginLiveTile}" Content="{Binding DataContext.Name, RelativeSource={RelativeSource AncestorType=ContentControl}}" Command="{Binding DataContext.SetStateCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
<DataTemplate x:Key="PluginActiveLiveTile" DataType="{x:Type local:PluginListItemViewModel}">
<Button Style="{StaticResource PluginActiveLiveTile}" Content="{Binding DataContext.Name, RelativeSource={RelativeSource AncestorType=ContentControl}}" Command="{Binding DataContext.SetStateCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
</UserControl.Resources>
<ContentControl d:DataContext="{x:Static local:PluginListItemDesignModel.Instance}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource PluginTile}" />
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="0">
<Setter Property="ContentTemplate" Value="{StaticResource PluginTile}" />
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource PluginActiveTile}" />
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource PluginLiveTile}" />
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="3">
<Setter Property="ContentTemplate" Value="{StaticResource PluginActiveLiveTile}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
PluginListItemViewModel: (The ViewModel for each list item)
public class PluginListItemViewModel : BaseViewModel
{
public string Name { get; set; }
public PluginTileStates State { get; set; }
public ICommand SetStateCommand { get; set; }
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
public PluginListItemViewModel()
{
SetStateCommand = new RelayCommand(() => SetState());
}
#endregion
private void SetState()
{
PluginListDesignModel.Instance.Items[0].State = PluginTileStates.Live;
State = PluginTileStates.NormalActive;
}
}
Steps to reproduce:
Create a new WPF project, .NET Framework 4.6.1 (Visual Studio 2017).
Replace the grid in MainWindow with the following:
<Grid DataContext="{x:Static local:ListViewModel.Instance}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ListItemControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
Add a new UserControl named ListItemControl and replace the grid with:
<UserControl.Resources>
<Style x:Key="Tile" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Red" />
</Style>
<Style x:Key="ActiveTile" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Green" />
</Style>
<DataTemplate x:Key="PluginTile" DataType="{x:Type local:ListItemViewModel}">
<Button Width="100" Height="60" Style="{StaticResource Tile}" Command="{Binding DataContext.SetStateCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
<DataTemplate x:Key="PluginActiveTile" DataType="{x:Type local:ListItemViewModel}">
<Button Width="100" Height="60" Style="{StaticResource ActiveTile}" Command="{Binding DataContext.SetStateCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
</UserControl.Resources>
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource PluginTile}" />
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="0">
<Setter Property="ContentTemplate" Value="{StaticResource PluginTile}" />
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource PluginActiveTile}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Add a new class called BaseViewModel and replace class with:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
public void OnPropertyChanged(string name)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
Add new class called ListItemViewModel and replace class with:
public enum TileStates
{
Normal = 0,
Active = 1
}
public class ListItemViewModel : BaseViewModel
{
public TileStates State { get; set; }
public ICommand SetStateCommand { get; set; }
public ListItemViewModel()
{
SetStateCommand = new RelayCommand(() =>
{
ListViewModel.Instance.Items[0].State = TileStates.Normal;
State = TileStates.Active;
});
}
}
Add new class called ListViewModel and replace class with:
public class ListViewModel : BaseViewModel
{
public static ListViewModel Instance => new ListViewModel();
public List<ListItemViewModel> Items { get; set; } = new List<ListItemViewModel>
{
new ListItemViewModel
{
State = TileStates.Active
},
new ListItemViewModel
{
State = TileStates.Normal
}
};
}
Add new class called RelayCommand and replace class with:
public class RelayCommand : ICommand
{
private Action mAction;
public event EventHandler CanExecuteChanged = (sender, e) => { };
public RelayCommand(Action action)
{
mAction = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
mAction();
}
}
Install the NuGet-Packages: "Fody v4.0.2" and "PropertyChanged.Fody v2.6.0" (You probably have to restart Visual Studio after installation
If you now press the bottom button, it should get green and the top one should switch to red.
ListViewModel.Instance returns a new instance of the ListViewModel class each time it's invoked. It should return the same instance:
public static ListViewModel Instance { get; } = new ListViewModel();

WPF Datagrid Grouping using a parent object as the expander header

I am using WPF Datagrid to group an observable collection by Parent. I have been following the example here and other examples showing a parent child relationship. So far I have the following:
And I want to get something like this:
Where the Header of a group is just a row as well but still able to collapse/expand child rows. I have tried to make a sub-datagrid without luck. So the underlying type of my collection looks something like this:
public class Task : INotifyPropertyChanged, IEditableObject
{
// memebers
...
public string ProjectName
{
get { return this.m_ProjectName; }
set
{
if (value != this.m_ProjectName)
{
this.m_ProjectName = value;
NotifyPropertyChanged("ProjectName");
}
}
}
public string TaskName
{
get { return this.m_TaskName; }
set
{
if (value != this.m_TaskName)
{
this.m_TaskName = value;
NotifyPropertyChanged("TaskName");
}
}
}
public DateTime DueDate
{
get { return this.m_DueDate; }
set
{
if (value != this.m_DueDate)
{
this.m_DueDate = value;
NotifyPropertyChanged("DueDate");
}
}
}
public bool Complete
{
get { return this.m_Complete; }
set
{
if (value != this.m_Complete)
{
this.m_Complete = value;
NotifyPropertyChanged("Complete");
}
}
}
public Task Parent
{
get { return m_Parent; }
set
{
m_Parent = value;
}
}
So my object type can either by a child or parent, where a child has a reference to its parent. So I am grouping by parent, I just haven't figured out how to make the expandable group headers rows of the same type. Any help is appreciated.
Here is the xaml:
<DataGrid x:Name="dataGrid1"
ItemsSource="{Binding Source={StaticResource cvsTasks}}"
CanUserAddRows="False"
ColumnWidth="*"
RowHeaderWidth="0">
<DataGrid.GroupStyle>
<!-- Style for groups at top level. -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
<Expander.Header>
<DockPanel HorizontalAlignment="Stretch" >
<TextBlock FontWeight="Bold" Text="{Binding Path=Name, Converter={StaticResource completeConverter}}" Margin="5,0,0,0" Width="200" HorizontalAlignment="Stretch"/>
<TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}" Width="Auto"/>
</DockPanel>
<!--<DataGrid x:Name="dataGrid2"
ItemsSource="{Binding Source={StaticResource vsParentTasks}}"
CanUserAddRows="False"
ColumnWidth="*"
RowHeaderWidth="0"
HeadersVisibility="None" >
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Foreground" Value="#FFEEEEEE" />
<Setter Property="Background" Value="#FF112255" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</DataGrid.RowStyle>
</DataGrid>-->
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="White" />
</Style>
</DataGrid.RowStyle>
</DataGrid>

Categories