I want to start off by saying I am new to C#, WPF, XAML, and MVVM. I haven't been able to find and answer to what I'm looking for, but I can't tell if that's because it hasn't been asked or if I'm looking in the wrong places. If there is something out there already, pointing me in the right direction would be much appreciated.
Now to my problem, I am trying to make a Navigation dashboard/panel with 4 criteria:
- By default the button views only as text
- When hovered, there is an indication that the text is a button
- When selected, indicate that the button is selected and maintain that state until another page is selected
- Have non-button labels above groups of categorically similar pages
Here is an example of the format I am looking for that I found online.
I started the MVVM conversion using an article I found here. I added the MVVM Light Libraries to my project and used their RelayCommand and ViewModelBase. This brought me to the issues of not knowing how to add intermediate labels to the list of items within the panel and the RadioButtons never deselect.
To add labels I tried CompositeCollections based off a stackoverflow discussion here with no success. I then tried duplicating the process I already had and then stack them with labels in between, also with no success.
Moving to the deselection issue I found another stackoverflow post here suggesting lists but my RadioButton style was not applying properly and I was still unable to figure out how to add labels.
Here is what I have for the list code:
<ListBox ItemsSource="{Binding PageViewModels}" SelectedItem="{Binding CurrentPageViewModel}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<RadioButton Style="{StaticResource NavRadio}"
Content="{TemplateBinding Content}"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
And this is my RadioButton template that I have been using everywhere for reference:
<Style x:Key="NavRadio" TargetType="{x:Type RadioButton}">
<Setter Property="MinHeight" Value="35"/>
<Setter Property="MinWidth" Value="200"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<Grid x:Name="NavButtonGrid">
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,0">
<GradientStop Offset="0"
Color="{DynamicResource ColorBackNavSelect}"/>
<GradientStop x:Name="SelectStop"
Offset="0"
Color="{DynamicResource ColorBackNavSelect}"/>
<GradientStop x:Name="DefaultStop"
Offset="0"
Color="Transparent"/>
<GradientStop Offset="1"
Color="Transparent"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<ContentPresenter x:Name="NavButtonContent"
Margin="30,0,0,0"
VerticalAlignment="Center"
HorizontalAlignment="Left"
TextElement.Foreground="{DynamicResource ForeNav}"
TextElement.FontSize="{DynamicResource NavFontSize}">
</ContentPresenter>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="DefaultStop"
Storyboard.TargetProperty="Offset"
To="1"
Duration="0:0:0.15"/>
<DoubleAnimation Storyboard.TargetName="SelectStop"
Storyboard.TargetProperty="Offset"
To="1"
Duration="0:0:0.15"
BeginTime="0:0:0.15"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="SelectStop"
Storyboard.TargetProperty="Offset"
To="0"
Duration="0:0:0.06"/>
<DoubleAnimation Storyboard.TargetName="DefaultStop"
Storyboard.TargetProperty="Offset"
To="0"
Duration="0:0:0.06"
BeginTime="0:0:0.06"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
<Setter TargetName="NavButtonContent" Property="TextElement.Foreground" Value="{DynamicResource ForeNavSelect}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="NavButtonContent" Property="TextElement.FontWeight" Value="Bold"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="NavButtonContent" Property="TextElement.Foreground" Value="{DynamicResource ForeNavSelect}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Any ideas of a way to get this done? I feel like its not that uncommon of a design so there must be some solution. I don't mind a solution in a completely different direction than what I have done, I just supplied the information to help.
Edit:
Based off of the comment made, I am not at a point where labels is the only issue standing in my way. This is the code for my MainWindowViewModel:
#region Fields
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
#endregion
public MainWindowViewModel()
{
// Add available pages
PageViewModels.Add(new HomeViewModel());
PageViewModels.Add(new ReportsViewModel());
PageViewModels.Add(new PurchasesViewModel());
PageViewModels.Add(new InventoryViewModel());
PageViewModels.Add(new PackingsViewModel());
PageViewModels.Add(new HistoryViewModel());
PageViewModels.Add(new ContactsViewModel());
PageViewModels.Add(new DefinitionsViewModel());
PageViewModels.Add(new AdminViewModel());
// Set starting page
CurrentPageViewModel = PageViewModels[0];
}
#region Properties / Commands
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand<object>(
param => ChangeViewModel((IPageViewModel)param),
param => param is IPageViewModel);
}
return _changePageCommand;
}
}
public List<IPageViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
_pageViewModels = new List<IPageViewModel>();
return _pageViewModels;
}
}
public IPageViewModel CurrentPageViewModel
{
get
{
return _currentPageViewModel;
}
set
{
if (_currentPageViewModel != value)
{
_currentPageViewModel = value;
RaisePropertyChanged("CurrentPageViewModel");
}
}
}
#endregion
#region Methods
private void ChangeViewModel(IPageViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);
CurrentPageViewModel = PageViewModels
.FirstOrDefault(vm => vm == viewModel);
}
#endregion
}
How would I be able to break up the PageViewModels list into separate lists while maintaining functionality? Would a nested list be a solution? Or could I give each page's ViewModel a string named "Group" similar to how it has "Name" and bind a control only to items within the PageViewModels list that have the same string for "Group"?