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>
Related
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>
I am new to WPF and I have read a lot of similar questions on the web, but I still do not get my listview work. I want to change the background color of a list view element depending of a property (red, yellow or green)
The itemsSource of my ListView is an observable list of this class:
public class ConnectionItem
{
public ConnectionItem(string name)
{
Name = name;
}
public string Name { get; }
private string _color = "Red";
public string Color { get => _color; }
private ConnectionStatus _status;
public ConnectionStatus Status
{
set
{
if (value == _status)
{
return;
}
else
{
switch (value)
{
case ConnectionStatus.Connected:
_color = "Yellow";
break;
case ConnectionStatus.Ready:
_color = "Green";
break;
default:
_color = "Red";
break;
}
}
}
}
}
And I have defined my listview in xaml as follows:
<ListView x:Name="lvConnections">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="ListViewItem_MouseDoubleClick" />
</Style>
</ListView.ItemContainerStyle>
<ListView.Resources>
<Style TargetType="ListViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Color}" Value="Green">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding Color}" Value="Red">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Color}" Value="Yellow">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Width="150" MaxHeight="50" Grid.Column="0" Grid.Row="0" Orientation="Horizontal" >
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" FontWeight="ExtraBlack" TextWrapping="Wrap" Padding="10"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The binding does not work and all my listview element have no background color. I do not need exactly the same solution via ListView.Resources binding, but I also have did not succed in other approaches.
If you move the trigger Style from ListView.Resources to StackPanel.Resources (and change the TargetType to StackPanel) then the background colors will display using this approach.
<StackPanel Width="150" MaxHeight="50" Grid.Column="0" Grid.Row="0" Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding Color}" Value="Green">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding Color}" Value="Red">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Color}" Value="Yellow">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" FontWeight="ExtraBlack" TextWrapping="Wrap" Padding="10"/>
</StackPanel>
You will also need to look at implementing INotifyPropertyChanged on ConnectionItem for the colors to update when Status is changed.
public class ConnectionItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ConnectionItem(string name)
{
Name = name;
}
public string Name { get; }
private string _color = "Red";
public string Color
{
get => _color;
set
{
if (value == _color) return;
_color = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Color"));
}
}
private ConnectionStatus _status;
public ConnectionStatus Status
{
get => _status;
set
{
if (value == _status) return;
_status = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Status"));
switch (value)
{
case ConnectionStatus.Connected:
Color = "Yellow";
break;
case ConnectionStatus.Ready:
Color = "Green";
break;
default:
Color = "Red";
break;
}
}
}
}
Note that Status and Color now have both get and set accessors and that
that Status is setting the Color property rather than directly setting the _color field.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
lvConnections.ItemsSource = new ObservableCollection<ConnectionItem>()
{
new ConnectionItem("Starts Connected") { Status = ConnectionStatus.Connected, },
new ConnectionItem("Starts Ready") { Status = ConnectionStatus.Ready, },
new ConnectionItem("Starts Default"),
};
}
private void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var item = (sender as ListViewItem)?.DataContext as ConnectionItem;
switch (item.Status)
{
case ConnectionStatus.Connected:
item.Status = ConnectionStatus.Ready;
break;
case ConnectionStatus.Ready:
item.Status = ConnectionStatus.Disconnected;
break;
default:
item.Status = ConnectionStatus.Connected;
break;
}
}
}
You can even go one step further, remove the Color property from ConnectionItem altogether (and the switch setting it in Status) and use Status values in the Style triggers.
ConnectionItem
public class ConnectionItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ConnectionItem(string name)
{
Name = name;
}
public string Name { get; }
private ConnectionStatus _status;
public ConnectionStatus Status
{
get => _status;
set
{
if (value == _status) return;
_status = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Status"));
}
}
}
Style
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="Ready">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Disconnected">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Connected">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
bind the background to the Color property.
<ListView x:Name="lvConnections">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="ListViewItem_MouseDoubleClick" />
</Style>
</ListView.ItemContainerStyle>
<ListView.Resources>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="{Binding Color}"/>
</Style>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Width="150" MaxHeight="50" Grid.Column="0" Grid.Row="0" Orientation="Horizontal" >
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" FontWeight="ExtraBlack" TextWrapping="Wrap" Padding="10"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
and set the Color property to a brush object
public class ConnectionItem
{
public ConnectionItem(string name)
{
Name = name;
}
public string Name { get; }
private Brush _color = Brushes.Red;
public Brush Color { get => _color; }
private ConnectionStatus _status;
public ConnectionStatus Status
{
set
{
if (value == _status)
{
return;
}
else
{
switch (value)
{
case ConnectionStatus.Connected:
_color = Brushes.Yellow;
break;
case ConnectionStatus.Ready:
_color = Brushes.Green;
break;
default:
_color = Brushes.Red;
break;
}
}
}
}
}
Your ListViewItem style doesn't get applied because your have set the ItemContainerStyle property to another Style. You should move your triggers to the ItemContainerStyle:
<ListView x:Name="lvConnections">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="ListViewItem_MouseDoubleClick" />
<Style.Triggers>
<DataTrigger Binding="{Binding Color}" Value="Green">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding Color}" Value="Red">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Color}" Value="Yellow">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Width="150" MaxHeight="50" Grid.Column="0" Grid.Row="0" Orientation="Horizontal" >
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" FontWeight="ExtraBlack" TextWrapping="Wrap" Padding="10"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have a grid view with a CollectionViewSource as it's items source.
I want to to bind the background property of each group container panel so that each group has its own background color.
how can this be achieved?
I'm trying to use binding in the <GroupStyle.ContainerStyle> of the gridview but can't get it to work.
Since the list will be grouped already, then applying a background on each GridViewItem will do the trick, depending on whether you want to define the backgound in each item as a property or use a converter to do that :
public class Data
{
public String Prop1 { get; set; }
public String Prop2 { get; set; }
public SolidColorBrush GroupeBrush { get; set; } //the groupe background color
}
And the xaml,
<Page.Resources>
<CollectionViewSource x:Name="DataCollection" IsSourceGrouped="true" />
</Page.Resources>
<Grid>
<GridView SelectionMode="None" ItemsSource="{Binding Source={StaticResource DataCollection}}" >
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
<Setter Property="VerticalContentAlignment" Value="Stretch"></Setter>
</Style>
</GridView.ItemContainerStyle>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate>
<Grid Background="{Binding GroupeBrush}">
<TextBlock Text="{Binding Prop2}" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
Or you could as well play around the GridView GroupStyle although you will need to find a way to bind the background from the Style Setter :
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Border Background="Black" HorizontalAlignment="Stretch">
<TextBlock Text='{Binding Key}' Foreground="White" Margin="5" Style="{StaticResource SubheaderTextBlockStyle}" />
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="MinWidth" Value="600"/>
<Setter Property="BorderBrush" Value="DarkGray"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="Margin" Value="3,0"/>
<Setter Property="Background" Value="BurlyWood"/>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</GridView.GroupStyle>
Here the entire code behind in case any one wants to experiment more
public sealed partial class MainPage : Page
{
private ObservableCollection<Data> _datas = new ObservableCollection<Data>()
{
new Data()
{
Prop1 = "val1",
Prop2 = "val2",
GroupeBrush=new SolidColorBrush(Colors.Blue)
}, new Data()
{
Prop1 = "val1",
Prop2 = "val2",
GroupeBrush=new SolidColorBrush(Colors.Blue)
}, new Data()
{
Prop1 = "val1",
Prop2 = "val3",
GroupeBrush=new SolidColorBrush(Colors.Blue)
}, new Data()
{
Prop1 = "val2",
Prop2 = "val4",
GroupeBrush=new SolidColorBrush(Colors.Green)
}, new Data()
{
Prop1 = "val3",
Prop2 = "val5",
GroupeBrush=new SolidColorBrush(Colors.Red)
},
};
public ObservableCollection<Data> Datas
{
get
{
return _datas;
}
set
{
if (_datas == value)
{
return;
}
_datas = value;
}
}
public MainPage()
{
this.DataContext = this;
InitializeComponent();
DataCollection.Source = GetAllGrouped();
}
public IEnumerable<IGrouping<string, Data>> GetAllGrouped()
{
return Datas.GroupBy(x => x.Prop1);
}
}
public class Data
{
public String Prop1 { get; set; }
public String Prop2 { get; set; }
public SolidColorBrush GroupeBrush { get; set; } //the groupe background color
}
OK, here's what I've got. I had to modify the template of the Group Container Style:
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Margin" Value="10,0,0,0" />
<Setter Property="Padding" Value="20"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<Border Background="{Binding Group, Converter={StaticResource ThemeColorConverter}}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentControl x:Name="HeaderContent"
Content="{TemplateBinding Content}"
ContentTransitions="{TemplateBinding ContentTransitions}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}"
Margin="{TemplateBinding Padding}"
TabIndex="0"
IsTabStop="False" />
<ItemsControl x:Name="ItemsControl"
Grid.Row="1"
ItemsSource="{Binding GroupItems}"
IsTabStop="False"
TabNavigation="Once"
TabIndex="1" >
<ItemsControl.ItemContainerTransitions>
<TransitionCollection>
<AddDeleteThemeTransition />
<ContentThemeTransition />
<ReorderThemeTransition />
<EntranceThemeTransition IsStaggeringEnabled="False" />
</TransitionCollection>
</ItemsControl.ItemContainerTransitions>
</ItemsControl>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
This line did the trick:
<Border Background="{Binding Group, Converter={StaticResource ColorConverter}}"/>
Binding to Group gives you access to the group's data source.
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.
Suppose my Model class called person looks like below code:
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public Gender Gender { get; set; }
}
Gender class used in Person class looks like:
public class Gender
{
public int ID { get; set; }
public string Name { get; set; }
public string ImageData { get; set; }
}
Now in a view called EditView I am trying to display the information of currently selected Person:
<Page ...............>
<Page.DataContext>
<vm:EditViewModel />
</Page.DataContext>
<Grid DataContext="{Binding CurrentPerson}">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<TextBox Text="{Binding Name}" />
<ComboBox Grid.Row="1"
ItemsSource="{Binding DataContext.Genders, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Page}}}"
SelectedItem="{Binding Gender}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Padding="6"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid x:Name="gd" TextElement.Foreground="Black">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Path Grid.Column="0" Data="{Binding ImageData}" Stretch="Uniform" Fill="Black" Width="24" Height="24" Margin="4" RenderTransformOrigin="0.5,0.5" />
<TextBlock Grid.Column="1" Text="{Binding Name}" Padding="6"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ComboBoxItem.IsSelected" Value="True">
<Setter TargetName="gd" Property="Background" Value="#FF4CC4F6"></Setter>
<Setter TargetName="gd" Property="TextElement.Foreground" Value="White"></Setter>
</Trigger>
<Trigger Property="ComboBoxItem.IsMouseOver" Value="True">
<Setter TargetName="gd" Property="Background" Value="#FF84CDFA"></Setter>
<Setter TargetName="gd" Property="TextElement.Foreground" Value="White"></Setter>
</Trigger>
<Trigger Property="ComboBoxItem.IsHighlighted" Value="True">
<Setter TargetName="gd" Property="Background" Value="LightSkyBlue"></Setter>
<Setter TargetName="gd" Property="TextElement.Foreground" Value="Black"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
</Page>
EditViewModel.cs code:
public class EditViewModel : ViewModelBase
{
public EditViewModel()
{
Genders = new ObservableCollection<Gender>(
from gender in XDocument.Load(DirectoryPaths.DataDirectory + #"Basic\Genders.xml")
.Element("Genders").Elements("Gender")
select new Gender
{
Id = Convert.ToInt32(gender.Attribute("Id").Value),
Name = gender.Element("Name").Value,
ImageData = gender.Element("ImageData").Value
}
);
}
private ObservableCollection<Gender> _genders;
public ObservableCollection<Gender> Genders
{
get
{
return _genders;
}
set
{
_genders = value;
NotifyPropertyChanged("Genders");
}
}
private Person _currentPerson;
public Person CurrentPerson
{
get
{
return _currentPerson;
}
set
{
_currentPerson = value;
NotifyPropertyChanged("CurrentPerson");
}
}
}
I have added the just relevent code for viewmodel. The property CurrentPerson has a person when EditView is shown. But in the ComboBox I do not get any selection by default. I am able to select values manually. But when the EditView loads I am not able to say the gender of the person as it does not get displayed as SelectedItem of the ComboBox.
You are running into a very common problem, two identical Gender objects are not equal to each other!
ComboBox sees the bound selected item, but doesn't see a matching item (actually, it doesn't see a matching reference) so it doesn't select anything.
To fix it, Gender needs to override Object.Equals (MSDN) and Object.GetHashCode (MSDN). According to this article implementing IEquatable will also work.