How to add grid to my combobox and display obervableCollection data? - c#

I use a combobox and I would like to proceed as follows:
I choose an element in cmb1, this allows to display in cmb2 a Collection.
This code allows me to retrieve the data I want ( result A = ObservableCollectionA, result B = ObservableCollection B...)
<ComboBox Name="cmbResultatListe"
Margin="0,10,0,10"
Grid.Row="4"
Grid.Column="2"
Height="25"
Width="250" >
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Sections}"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedChoiceList}" Value="Etablissement">
<Setter Property="ItemsSource" Value="{Binding Etablissements}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedChoiceList}" Value="Service">
<Setter Property="ItemsSource" Value="{Binding Services}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
I would now like to divide my combobox into three grids, so that I can proceed as follows:
If result A is selected => cmb2 grid0 = ObservableCollectionA.ID, cmb2 grid1 = observableCollectionA.Name...
If result B is selected => cmb2 grid0 = ObservableCollectionB.Name, cmb2 grid1 = observableCollectionB.Years...
And i don't know how can i do that.
Any tips ?
Thank you for your help.
Edit :
c# code :
private ObservableCollection<Etablissement> _EtablissementsUtilisateur;
public ObservableCollection<Etablissement> EtablissementsUtilisateur
{
get
{
return _EtablissementsUtilisateur;
}
set
{
if (value != _EtablissementsUtilisateur)
{
_EtablissementsUtilisateur = value;
RaisePropertyChanged(nameof(EtablissementsUtilisateur));
}
}
}
private ObservableCollection<ServiceSectionModel> _Services;
public ObservableCollection<ServiceSectionModel> Services
{
get
{
return _Services;
}
set
{
if (value != _Services)
{
_Services = value;
RaisePropertyChanged(nameof(Services));
}
}
}
private ObservableCollection<ServiceSectionModel> _Sections;
public ObservableCollection<ServiceSectionModel> Sections
{
get
{
return _Sections;
}
set
{
if (value != _Sections)
{
_Sections = value;
RaisePropertyChanged(nameof(Sections));
}
}
}
private string _SelectedChoiceList;
public string SelectedChoiceList
{
get
{
return _SelectedChoiceList;
}
set
{
if (value != _SelectedChoiceList)
{
_SelectedChoiceList = value;
RaisePropertyChanged(nameof(SelectedChoiceList));
}
}
}
Etablissements = new ObservableCollection<Etablissement>((await _dataService.GetEtablissements().ConfigureAwait(false)));
Services = await _dataService.GetServicesAsync(false).ConfigureAwait(false);
Sections = await _dataService.GetSectionsAsync(_dataService.ParamGlobaux.IDEtablissement).ConfigureAwait(false);
Etablissement contain ID, Name, Years.
Service contain Color, ID, Name.
Section contain Color, ID, SectionName.
Edit 2 : I would like something like this exemple :
<ComboBox Name="CbService" HorizontalAlignment="Left" Margin="115,67,0,0" VerticalAlignment="Top" Width="150" ItemsSource="{Binding}" SelectionChanged="CbRecherche_SelectionChanged" KeyboardNavigation.TabIndex="1" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Libelle}" />
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid x:Name="gd" TextElement.Foreground="Black" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="Auto" MinWidth="50" />
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle Fill="{Binding Fond}" Grid.Column="0"/>
<TextBlock Margin="5" Grid.Column="0" Text="{Binding ID}"/>
<TextBlock Margin="5" Grid.Column="1" Text="{Binding Libelle}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
Currently my combobox displays a string. I want something like this :
In this example, there is an ID, a color only in the ID part, and a Name. I can't do that with my string at the moment.

I belive you may be able to reduce the size of your codes by removing the RaisePropertyChanged event as ObservableCollections already contain the INotifyPropertyChanged interface, I have made a simple example of how to use Datatemplate to display information from ObservableCollections.
Step 1: C# codes:
namespace WpfApp1
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
ComboBox1.ItemsSource = Services;
Services.Add(new ServiceSectionModel { Color = Brushes.Red, ID = "Clem", Name = "Clementine" });
Services.Add(new ServiceSectionModel { Color = Brushes.White, ID = "011", Name = "Logistique" });
Services.Add(new ServiceSectionModel { Color = Brushes.Green, ID = "MBT", Name = "Montbrilland" });
}
public class ServiceSectionModel
{
public string ID { get; set; }
public string Name { get; set; }
public SolidColorBrush Color { get; set; }
}
ObservableCollection<ServiceSectionModel> Services = new ObservableCollection<ServiceSectionModel>();
}
}
Step 2: XAML codes:
<ComboBox x:Name="ComboBox1" HorizontalAlignment="Center" VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Background="{Binding Color}" Text="{Binding ID}" Margin="0,0,10,0" Padding="5,2,10,2"></TextBlock>
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Related

wpf mvvm - Is there a way to programmatically create textbox based on number of properties in model and bind to each of them in?

I have multiple objects with multiple properties which I want to display or hide (or create if needed) based on the property value.
For example:
Model:
public class InputModel
{
public string Name { get; set; }
public string Id { get; set; }
public string Type { get; set; }
public bool Required { get; set; }
public int Dpi { get; set; }
public string Data { get; set; }
public List<ColorModel> Filter { get; set; }
public List<RenderModel> Render { get; set; }
public List<LayoutModel> Layout { get; set; }
}
View:
<ItemsControl x:Name="InputsList"
Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Height="400">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Center" Background="Beige" Margin="10">
<TextBlock Text="{Binding Name, StringFormat='Name: {0}'}"/>
<TextBlock Text="{Binding Id, StringFormat='Id: {0}'}"/>
<TextBlock Text="{Binding Type, StringFormat='Type: {0}'}"/>
<CheckBox Grid.Column="1" Content="Required" Margin="0,5,0,0"
IsChecked="{Binding ElementName=Required, Path=CheckBoxIsChecked}"/>
<TextBox Grid.Column="0" Grid.Row="4"
Text="{Binding Dpi, StringFormat='Dpi: {0}'}"/>
<TextBlock Grid.Column="0" Grid.Row="5"
Text="Render:"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And my ViewModel:
public class InputsListViewModel : Screen
{
private IRootModel _rootModel;
public InputsListViewModel(IRootModel rootModel)
{
_rootModel = rootModel;
}
private BindingList<InputModel> _inputs;
public BindingList<InputModel> InputsList
{
get
{
_inputs = new (_rootModel.Inputs);
return _inputs;
}
set
{
_inputs = value;
NotifyOfPropertyChange(() => InputsList);
}
}
private BindingList<RenderModel> _renders;
public BindingList<RenderModel> RenderList
{
get
{
_renders = new(_rootModel.Render);
return _renders;
}
set
{
_renders = value;
NotifyOfPropertyChange(() => RenderList);
}
}
private BindingList<LayoutModel> _layouts;
public BindingList<LayoutModel> LayoutList
{
get
{
_layouts = new (_rootModel.Layout);
return _layouts;
}
set
{
_layouts = value;
NotifyOfPropertyChange(() => LayoutList);
}
}
}
I don't need to display all of the properties in here, just the ones that I need at a given time. And I want to be able to add any of the properties that are missing, if I need to.
I am a beginner and so far I only know how to manually create the fields that I need in view and bind to them.
Thanks!
you can hide whole items with ItemContainerStyle or individual Properties as shown in 2 different ways
<ItemsControl ItemsSource="{Binding MyItems}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Visibility" Value="{Binding ShowInUi, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name, StringFormat='Name: {0}'}"/>
<TextBlock Text="{Binding Id, StringFormat='Id: {0}'}"/>
<TextBlock Text="{Binding Type, StringFormat='Type: {0}'}"/>
<!--possibility 1-->
<TextBlock x:Name="AdditionalInfoText" Text="{Binding AdditionalInfo}"/>
<!--possibility 2-->
<TextBlock Text="{Binding SuperDuperValue}" Visibility="{Binding IsSuperDuperItem, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding AdditionalInfo}" Value="{x:Null}">
<Setter TargetName="AdditionalInfoText" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
For all textblocks in an area
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
<DataTrigger Binding="{Binding Text.Length, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>

WPF Add Header to Custom ComboBox

I have a custom ComboBox that have each item (Favorites and not favorites) is a Label + Button, then the last item have only a button to load all elements. Now I want to add a header as the first item, that says "Favorites".
Right now I have:
<ComboBox
x:Name="ComboBoxBtn"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="0,0,0,-1"
Width="300"
ItemsSource="{Binding Source, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding Path=Selected, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
IsSynchronizedWithCurrentItem="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Name="PART_GRID">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Content="{Binding}"
Width="250" Visibility="{Binding Path=., Converter={StaticResource elementToVisibilityConverter}}" />
<Button Name="PART_BUTTON"
Grid.Column="1"
Content="+"
Command="{Binding AddCommandButton, ElementName=root}"
CommandParameter="{Binding}"
Visibility="{Binding Path=., Converter={StaticResource elementToVisibilityConverter}}"/>
<Button Content="Carregar Todos" Margin="5,5"
Command="{Binding LoadAllCommandButton, ElementName=root}"
CommandParameter="{Binding ElementName=root, Path=FavoriteType}"
Visibility="{Binding Path=.,Converter={StaticResource elementToVisibilityForAddConverter}}"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Favorite}"
Value="True">
<Setter TargetName="PART_GRID"
Property="Background"
Value="#FFE6E6FA" />
<Setter TargetName="PART_BUTTON"
Property="Content"
Value="-" />
<Setter TargetName="PART_BUTTON"
Property="Command"
Value="{Binding RemoveCommandButton, ElementName=root}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I preferred a different aproach, which i think is easier and more clean:
i created an empty interface IDrawable.
all classes i need to put inside the combobox should inherit from IDrawable
i created these:
MyLabel:
public class MyLabel : IDrawable
{
public string text { get; set; }
public MyLabel()
{
this.text = "MYTEXT";
}
}
MyButton:
public class MyButton : IDrawable
{
public string text { get; set; }
public MyButton()
{
this.text = "MYNBUTTON";
}
}
MyLabelButton:
public class MyLabelButton : IDrawable
{
public string labelText { get; set; }
public string buttonText { get; set; }
public MyLabelButton()
{
labelText = "labelText";
buttonText = "buttonText";
}
}
than here is the xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:WpfApplication1">
<Window.Resources>
<DataTemplate DataType="{x:Type me:MyButton}">
<Button Content="{Binding text}" />
</DataTemplate>
<DataTemplate DataType="{x:Type me:MyLabel}">
<TextBlock Text="{Binding text}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type me:MyLabelButton}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding labelText}"/>
<Button Content="{Binding buttonText}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Name="MyGrid">
<ComboBox Name="MyCombo" ItemsSource="{Binding list}" SelectedItem="{Binding sel}" PreviewMouseLeftButtonUp="ComboBox_PreviewMouseLeftButtonUp"/>
</Grid>
</Window>
and codebehind:
public partial class MainWindow : Window
{
private IDrawable clicked;
public ObservableCollection<IDrawable> list { get; set; }
public IDrawable sel { get; set; }
public MainWindow()
{
InitializeComponent();
list = new ObservableCollection<IDrawable>();
list.Add(new MyLabel());
list.Add(new MyLabelButton());
list.Add(new MyButton());
this.DataContext = this;
}
private void ComboBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point pt = e.GetPosition(MyGrid);
clicked = null;
VisualTreeHelper.HitTest(
MyGrid,
null,
new HitTestResultCallback(ResultCallback),
new PointHitTestParameters(pt));
if (clicked != null)
{
((ComboBox)sender).IsDropDownOpen = false;
//do something
}
}
private HitTestResultBehavior ResultCallback(HitTestResult result)
{
DependencyObject parentObject = VisualTreeHelper.GetParent(result.VisualHit);
if (parentObject == null)
return HitTestResultBehavior.Continue;
var v = parentObject as Button;
if (v == null)
return HitTestResultBehavior.Continue;
if (v.DataContext != null && v.DataContext is IDrawable)
{
clicked = (IDrawable)v.DataContext;
return HitTestResultBehavior.Stop;
}
return HitTestResultBehavior.Continue;
}
}
the result is
as you can see I have standard combobox and custom elements. which i think it's better. in codebehind you can handle everything, like the impossibility of select the first label, call the command associated with the button, if the button is pressed, and so on.
In ComboBox_PreviewMouseLeftButtonUp i handled the click on the selected item, in case you want to do something particular if the selected button is pressed, and not show the dropdown menu.
this example is pretty barebones you need to customize it a little more and use MVVM everywhere.
at the moment you can push the button in the dropDown menu, you probabily want to disable the click if that buttont isn't selected.
EDIT
ComboBox_PreviewMouseLeftButtonUp should be like this:
private void ComboBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point pt = e.GetPosition((ComboBox)sender);
clicked = null;
VisualTreeHelper.HitTest(
(ComboBox)sender,
null,
new HitTestResultCallback(ResultCallback),
new PointHitTestParameters(pt));
if (clicked != null)
{
((ComboBox)sender).IsDropDownOpen = false;
//do something
}
}
(replaced Mygrid with (ComboBox)sender

Header element within a ListView in UWP

I'd like to have a header element in my ListView, but I need the possibility to decide which item is simple and which one is header when I bind data to the ListView. This header should not be clickable and have a different color at least. In Android we do it in a custom adapter.
At the moment I get this logic of data binding to the ListView:
<ListView
x:Name="drawerListOptions"
SelectionChanged="onSelectDrawerItem"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<!--for simplicity I put only one view in as a list item -->
<TextBlock
Text="{Binding titleItemMenu}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
and C# code:
drawerListOptions.ItemsSource = myListOfData;
So I need to check in my myListOfData if item is simple or header. Is there a way to do it?
by using CollectionViewSource
although i have the same problem, i searched over web but couldn't find solution so i create this one to load data from server and show in list as group separated as StickyHeaderListView in android.
in XAML
<Page
....
>
<RelativePanel ... >
<Page.Resources>
<CollectionViewSource x:Key="cvs" x:Name="cvs" SourceGrouped="True"/>
</Page.Resources>
<ListView x:Name="listView" IsItemClickEnabled="True" ItemsSource="{Binding Source={StaticResource `cvs` }}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate >
<RelativePanel HorizontalAlignment="Stretch" VerticalAlignment="Center" BorderBrush="DarkGray" BorderThickness="0,0,0,1" Background="White" Padding="10">
<TextBlock Text="{Binding Description}" RelativePanel.AlignVerticalCenterWithPanel="True" VerticalAlignment="Center" Foreground="Gray" FontSize="20"></TextBlock>
<StackPanel Orientation="Horizontal" RelativePanel.AlignRightWithPanel="True" RelativePanel.AlignVerticalCenterWithPanel="True" Margin="0,0,8,0" >
<TextBlock Text="{Binding Amount}" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="{StaticResource primary}" FontSize="16"/>
</StackPanel>
</RelativePanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate >
<DataTemplate>
<TextBlock Text="{Binding Key}" FontSize="14" Foreground="#FF222222" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="ListViewHeaderItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</RelativePanel> </Page>
and in source file
bind source
var obj = JsonConvert.DeserializeObject<List<Model.YourModelHere>>(ApiResponseHere);
var groups = from Data in obj group Data by Data.Date;
this.cvs.Source = groups;
Have an property in you collection item called IsSimple. In your xaml bind that variable to Header property of list view with converter. That Converter converts bool to Visibility. If value us true converter should return Visibility.Collapsed.
In UWP, we can use CollectionViewSource to provides a data source that adds grouping and current-item support to collection classes.
Use CollectionViewSource when you want to bind list controls to collections, but you want to display those collections in groups and maintain a current item independent from the list control.
For more info, see CollectionViewSource.
In my words, when you want bind to the collections and display those collections in groups without manipulating, we can use CollectionViewSource in this scenario.
You can use LINQ query to get results from the ObservableCollection. And you can set the results to the CollectionViewSource.Source.
In ListView, we can set the ListView.ItemSource bind to CollectionViewSource.View. It can get the view object that is currently associated with this instance of CollectionViewSource.
For example:
Create GroupInfoList class:
public class GroupInfoList : List<object>
{
public object Key { get; set; }
}
Create Text class:
public class Text : INotifyPropertyChanged
{
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
RaisePropertyChanged("Title");
}
}
public Text(string name)
{
this.Title = name;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Add Header class:
public class Header
{
public string HeaderTitle { get; set; }
public Header()
{
HeaderTitle = string.Empty;
}
public static ObservableCollection<Text> GetTexts()
{
ObservableCollection<Text> myListOfData = new ObservableCollection<Text>();
myListOfData.Add(new Text("Product1"));
myListOfData.Add(new Text("Product2"));
myListOfData.Add(new Text("Product3"));
myListOfData.Add(new Text("Setting1"));
myListOfData.Add(new Text("Setting2"));
myListOfData.Add(new Text("Setting3"));
myListOfData.Add(new Text("Setting4"));
return myListOfData;
}
public static ObservableCollection<GroupInfoList> GetItemsGrouped()
{
ObservableCollection<GroupInfoList> groups = new ObservableCollection<GroupInfoList>();
var query = from item in GetTexts()
group item by item.Title[0] into g
orderby g.Key
select new { GroupName = g.Key, Items = g };
foreach (var g in query)
{
GroupInfoList info = new GroupInfoList();
if (g.GroupName.ToString() == "P")
{
info.Key = "Products";
}
else if (g.GroupName.ToString() == "S")
{
info.Key = "Settings";
}
else
{
info.Key = g.GroupName;
}
foreach (var item in g.Items)
{
info.Add(item);
}
groups.Add(info);
}
return groups;
}
}
In XAML:
<Page.Resources>
<CollectionViewSource x:Name="MyItems" IsSourceGrouped="True" />
<DataTemplate x:Name="ItemListViewTemplate">
<TextBlock Text="{Binding Title}"></TextBlock>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{x:Bind MyItems.View}"
SelectionMode="Single"
ShowsScrollingPlaceholders="True"
Grid.Row="1"
ItemTemplate="{StaticResource ItemListViewTemplate}"
Grid.ColumnSpan="2">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Foreground="Red" Text="{Binding Key}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
Set the CollectionViewSource source in code behind:
public MainPage()
{
this.InitializeComponent();
MyItems.Source = Header.GetItemsGrouped();
}

ItemsControl databinding doesn't work?

I have this WPF structure:
<UserControl
xmlns:viewModel="clr-namespace:..ViewModel.ToneAudiogramLegend">
...
<DataTemplate DataType="{x:Type viewModel:ToneAudiogramLegendTableViewModel}">
...
<DataGrid Grid.Row="1" Grid.Column="0" ItemsSource="{Binding ToneAudiogramLegneds}" HeadersVisibility="None" AutoGenerateColumns="False" IsReadOnly="True" BorderBrush="Transparent" BorderThickness="0"
MinWidth="100" Height="{Binding Height, Mode=OneWay}" KeyboardNavigation.DirectionalNavigation="None" Grid.ColumnSpan="6" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" GridLinesVisibility="None" ColumnWidth="*"
Margin="1" wpfmvvm:DataGridRowHeightBehaviour.AutoFitRowHeight="True" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<DataGrid.Columns>
<DataGridTemplateColumn HeaderTemplate="{x:Null}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label x:Name="PART_Content" Content="{Binding Path=Left.Content}"
HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="Gold"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsMonochrome}" Value="True">
<Setter TargetName="PART_Content" Property="Foreground" Value="Green"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
...
<DataTemplate>
The binding of the Value works because the color gets the default value of Left.Color of <Setter Property="Foreground" Value="{Binding Path=Left.Color}"/>
The IsMonochrome property changes value when a specific event occurs, but the Foreground color does not change to green. I am not quite sure the format and the structure is appropriate. I am not sure if <DataTemplate> is a problem, since I have another <DataTemplate> in higher order
The classes:
public partial class ToneAudiogramLegendTableViewModel : ViewModelBase, IToneAudiogramLegendTableViewModel, IHandleMonochromeReportElement
{
public bool IsMonochrome
{
get { return GetValue<bool>("IsMonochrome"); }
private set { SetValue("IsMonochrome", value); }
}
public void SwitchToMonochromeMode()
{
IsMonochrome = true;
}
public void SwitchToColorMode()
{
IsMonochrome = false;
}
}
and
public class ToneAudiogramLegendViewModel : ViewModelBase, IToneAudiogramLegendVM
{
public string Name
{
get { return GetValue<string>("Name"); }
set { SetValue("Name", value); }
}
public LegendViewModel Left
{
get { return GetValue<LegendViewModel>("Left"); }
set { SetValue("Left", value); }
}
}
and
public class LegendViewModel : ViewModelBase
{
public object Content
{
get { return GetValue<object>("Content"); }
set { SetValue("Content", value); }
}
public Brush Color
{
get { return GetValue<Brush>("Color"); }
set { SetValue("Color", value); }
}
public LegendViewModel(object content, Brush color)
{
Content = content;
Color = color;
}
}
What might be the issue?
In your CellTemplate you are binding to ToneAudiogramLegendViewModel object, where are properties Name and Left. The IsMonochrome is on different object, that's why it does not work.
You need to either define IsMonochrome property or you need create property to reference ToneAudiogramLegendTableViewModel in ToneAudiogramLegendViewModel to be able to databind to IsMonochrome.
EDIT:
based on your comments:
<DataTrigger Binding="{Binding Path=DataContext.IsMonochrome,
RelativeSource={RelativeSource DataGrid}}"
Value="True">

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.

Categories