ComboBox binding inside Item template with IEnumerable collection - c#

I have one class MovieSystem which inherits from AvailableMovies collection.
public class MovieSystem : IEnumerable<AvailableMovies >, IEnumerable
{
public AvailableMovies this[int i] { get; }
public AvailableMovies this[TheatreLocation location] { get; }
public AvailableMovies [] ReleasedMovies { get; }
}
TheatreLocation is an enum with values like
public enum TheatreLocation
{
Citycentre = 0,
CityNorth = 1,
CitySouth = 2,
CityEast = 3,
CityWest = 4,
}
AvailableMovies collection is with below properties
public class AvailableMovies
{
public AvailableMovies ();
public int Index { get; }
public TheatreLocation Location { get; set; }
public string TheatreID { get; set; }
public double Ticketprice{ get; }
}
My Xaml code looks like below
<ItemsControl ItemsSource="{Binding MovieSystem }">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path= ""What should I bind here? ""}"
DisplayMemberPath="{Binding Path = ???}"
SelectedIndex="{Binding Path =Location , Mode=TwoWay, Converter={StaticResource MovieSystemLocationConverter}}">
</ComboBox>
<ComboBox
ItemsSource="{Binding Path = ???}"
SelectedIndex="{Binding Path=TheatreID , Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="{Binding Path= ??}">
</ComboBox>
<TextBox Grid.Column="3"
Text="{Binding Path= Ticketprice, Mode =TwoWay,StringFormat=F3, NotifyOnValidationError=True}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have a view model, xaml datacontext is set to this view model.
internal class MovieSystemPanelViewModel
{
///tried this collection but not worked.
public ObservableCollection<LocationViewModel> Locations { get; } = new ObservableCollection<LocationViewModel>();
public MovieSystem MovieSystem { get; }
public MovieSystemPanelViewModel() {}
}
Now my problem is both ComboBox which should display Theatre id and location not displaying due to binding issue. If try to bind that Locations observable collection inside ComboBox itemsource its not allowing, inside items template only AvailableMovies properties are in bindable condition.
Ticketprice is binding properly and displaying.

The issue here is since you are binding Items.Control with a collection, child elements of that Items.Control will have access to that collection's property only. So, if you want to use another collection you need to use relative source to get access in datacontext level. And since it's an enum to properly display, you need to display the dictionary values using an extension method and also using another Itemtemplate inside ComboBox to get the proper display.
internal class MovieSystemPanelViewModel
{
///Use dictionary instead of observable collection
public Dictionary<TheatreLocation , string> Locations { get; } = new Dictionary<TheatreLocation , string>();
public MovieSystem MovieSystem { get; };
public MovieSystemPanelViewModel()
{
// Fill up data in dictionary
foreach (Enum item in Enum.GetValues(typeof(TheatreLocation)))
{
Locations.Add((TheatreLocation)item, ((TheatreLocation)item).GetDisplayName());
}
}
}
Write a Extension method in project somewhere, for display member according to requirement.
public static string GetDisplayName(this TheatreLocation location)
{
switch (location)
{
case TheatreLocation.CityCentre:
return "Center city location";
case TheatreLocation.CityNorth :
return "Northern city location";
case TheatreLocation.CitySouth :
return "Southern city location";
case TheatreLocation.CityEast :
return "Eastern city location";
case CameraLocation.CityWest :
return "Western city location";
default:
return "Unknown";
}
}
Now Bind xaml with these properties and methods.
<ComboBox
ItemsSource="{Binding Path= DataContext.Locations, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
SelectedIndex="{Binding Path =Location , Mode=TwoWay, Converter={StaticResource MovieSystemLocationConverter}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Value}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Related

C# WPF MVVM ItemSource not attaching list

I have a list of Roles that I want to have selectable in a DataGrid cell ComboBox.
I have the ObservableCollection Roles for the ComboBox being populated in the ViewModel.
For some reason, nothing shows up in the ComboBox though. What's the proper way of attaching this collection of objects?
Any comments or suggestions would be helpful.
PersonModel:
public int SelectedRole { get; set; }
RoleModel:
public int Id { get; set; }
public int Role { get; set; }
public string Description { get; set; }
public string RoleInfo
{
get
{
return $"{Role} - {Description}";
}
}
ViewModel:
private ObservableCollection<PersonModel> _people;
public ObservableCollection<PersonModel> People
{
get { return _people; }
set
{
_people = value;
NotifyOfPropertyChange(() => People);
}
}
public ObservableCollection<RoleModel> _roles;
public ObservableCollection<RoleModel> Roles
{
get
{
return _roles;
}
set
{
_roles = value;
NotifyOfPropertyChange(() => Roles);
}
}
public PersonViewModel(IEventAggregator events, IWindowManager windowmanager)
{
_events = events;
_windowManager = windowmanager;
sql = "SELECT * FROM People";
People = SqliteConnector.LoadData<PersonModel>(sql, new Dictionary<string, object>());
sql = "SELECT * FROM Roles";
Roles = SqliteConnector.LoadData<PersonModel>(sql, new Dictionary<string, object>());
}
View:
<DataGrid ItemsSource="{Binding Path=People}"
AutoGenerateColumns="False"
CanUserDeleteRows="True"
CanUserReorderColumns="True"
CanUserAddRows="True"
AlternatingRowBackground="#dfdfdf"
cm:Message.Attach="[Event RowEditEnding] = [Action SaveOrUpdate()]">
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Role}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="RoleInfo"
ItemsSource="{Binding Path=Roles}"
SelectedValue="{Binding Path=SelectedRole, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Role" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid>
The DataContext of the "row" elements of the DataGrid is the corresponding item of the ItemsSource collection, i.e. a PersonModel instance - which does of course not have a Roles property. You should have observed a corresponding data binding error message in the Output Window in Visual Studio.
In order to bind to the Roles property of the parent view model, use an expression like this:
ItemsSource="{Binding DataContext.Roles,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
The SelectedValue Binding could simply be like shown below, because PropertyChanged is already the default UpdateSourceTrigger for that property.
SelectedValue="{Binding Path=SelectedRole}"
When you run your program, check the output window to see exactly what kind of error is coming up for this control when it loads. This will give you a better idea of the exception type being thrown. My guess is that the ItemsSource of the ComboBox is looking for the ObservableCollection Roles in your Person Model, and is throwing an exception since Roles is in only in your ViewModel. Try moving this collection to the Person Model and assign it's initial value via the Person Model constructor.

Binding ItemsControl Items Property to outside Source Object

I have a List of objects of type MenuModel called MenuList inside my ViewModel. I am using CaliburnMicro framework
I would like to show this list as a list of ToggleButtons that have IsChecked property bound to other object list called SelectedMenusMonday, which is list of type SelectedMenuModel that has only IsSelected property and is the same length as MenuList.
MenuModel looks like this:
public class MenuModel
{
public int MenuKey { get; set; }
public string MenuName { get; set; }
public string Description { get; set; }
}
MenuList:
public List<MenuModel> MenuList
{
get { return _MenuList; }
set => Set(ref _MenuList, value);
}
SelectedMenuModel
public class SelectedMenuModel
{
public bool IsSelected { get; set; }
}
And SelectedMenusMonday list:
private BindableCollection<SelectedMenuModel> _SelectedMenusMonday = new BindableCollection<SelectedMenuModel>();
public BindableCollection<SelectedMenuModel> SelectedMenusMonday
{
get { return _SelectedMenusMonday; }
set => Set(ref _SelectedMenusMonday, value);
}
I am trying to display like this:
<ItemsControl x:Name="MondayMenuList" ItemsSource="{Binding MenuList}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Content="{Binding MenuName}" IsChecked="{Binding Path=DataContext.SelectedMenusMonday.IsSelected, RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The MenuList and SelectedMenus Monday get filled from SQL DB. This is the solution i tried, but it does not work. Can someone help me please! I want the ToggleButtons to be "checked" if the item on the SelectedMenusMonday have IsSelected property as true.
Thank you very much!
Name the root element in your view (or wherever you know the DataContext to be correct) and use ElementName binding as shown here:
<UserControl x:Name="view">
<Grid>
<ItemsControl x:Name="MondayMenuList" ItemsSource="{Binding MenuList}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Content="{Binding MenuName}" IsChecked="{Binding ElementName=view, Path=DataContext.SelectedMenusMonday}">
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
Note the x:Name="view" in the UserControl element.

getting null from property binded for selected wpf combobox value

I have class DimensionType, it has properties Name, Id, etc. I constructed Property in my ViewModel = "dimStyleId" to retrieve selected form ComboBox. I am getting null in this property although I checked it in TextBlock and get it.
<!--Dimension Type Combobox-->
<ComboBox x:Name="DimensionType"
ItemsSource="{Binding dimTypes , Mode=TwoWay}"
SelectedValue="{Binding dimStyleId , Mode=TwoWay}"
SelectedValuePath="DimensionType"
DisplayMemberPath="Name"
Padding="3" />
and here is my VM Class
public class GridsDimViewModel : INotifyPropertyChanged
{
public ElementId dimensionType;
private ElementId _dimStyleId { get; set; }
public ElementId dimStyleId
{
get
{
return _dimStyleId;
}
set
{
if (_dimStyleId != value)
{
_dimStyleId = value;
NotifyPropertyChanged(nameof(dimStyleId));
}
}
}
}
and here is my check textbox which gets the id in it
<TextBlock Text="{Binding dimStyleId}"
Padding="3" />
Swap
SelectedValuePath="DimensionType"
to
SelectedValuePath="Id"

WPF: TreeView does not show any children

I've been trying to get my first TreeView to work, at first without the ViewModel. But no matter what I do, it doesn't show any children, even though the binding is correct.
I'm using two Item templates, one for hierarchical and another for data. You can see the important parts for both below:
class GrupoTag
{
public string Nome { get; set; }
public GrupoTag Pai { get; set; }
public List<Tag> ListaFilhos { get; set; }
public List<GrupoTag> SubGrupos { get; set; }
public GrupoTag(string nome)
{
Nome = nome;
ListaFilhos = new List<Tag>();
SubGrupos = new List<GrupoTag>();
}
public List<object> Filhos
{
get
{
List<object> lista = new List<object>();
foreach (GrupoTag subGrupo in SubGrupos)
lista.Add(subGrupo);
foreach (Tag filho in ListaFilhos)
lista.Add(filho);
}
}
}
class Tag
{
public GrupoTag Pai { get; set; }
public string Nome { get; set; }
public Tag(string nome)
{
Nome = nome;
}
public Tag(GrupoTag pai, string nome)
{
Pai = pai;
Nome = nome;
}
}
The XAML binding to all of this:
<TreeView Name="MenuTags">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="xml2Excel2:GrupoTag" ItemsSource="{Binding Filhos}">
<TextBlock Text="{Binding Nome}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="xml2Excel2:Tag">
<TextBlock Text="{Binding Nome}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
But the property "Filhos" on the GrupoTag class is never accessed. I've tried putting a breakpoint in there, throwing an exception, but it's simply never called. And the TreeView only displays the names of the collection of GrupoTags I assigned to it as its ItemSource in code-behind.
MenuTags.ItemsSource = arvoreTeste.SubGrupos;
I've read all the related questions and corrected the code for the respective errors, but I'm still lost here.
EDIT 1: So I modified the code of the classes to conform to the simple interface below:
class ITag
{
public string Nome { get; set; }
public ObservableCollection<ITag> Filhos { get; set; }
}
As per Benin comment, GrupoTag now uses a single property stored in a ObservableCollection to represent its children. And, as per Adnan answer removed the DataType from the XAML TreeView. Now it looks like this:
<TreeView Name="ArvoreTags">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Nome}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
It works, the TreeViewis functional. But I don't know why.
You need to give your TreeView ItemSource and DataType. And I would agree to Berins comment , you should avoid making new list to each access to Filhos property. TreeView works very good with Polymorphis, I mean with data types.
<TreeView DockPanel.Dock="Top"
DataContext="{Binding ProjectNodes_DataContext}"
ItemsSource="{Binding Nodes}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ProjectNode}" ItemsSource="{Binding SubItems}">
<StackPanel>
<TextBlock Text="{Binding Header}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
My Property is:
private ProjectNodesVM mProjectNodes_DataContext;
public ProjectNodesVM ProjectNodes_DataContext
{
get { return mProjectNodes_DataContext; }
protected set
{
SetProperty(ref mProjectNodes_DataContext, value);
}
}
and class ProjectNodesVM has:
public ObservableCollection<ProjectNode> Nodes {
get { return mNodes; }
protected set { SetProperty(ref mNodes, value); }
}
inside ProjectNode class i have:
private string mHeader;
public string Header
{
get { return mHeader; }
set { SetProperty(ref mHeader, value); }
}
I found a simple answer to my specific case, not yours it seems
The children must be a "property".
So:
public ObservableCollection<Tag> ListaFilhos ; //doesn't work
public ObservableCollection<Tag> ListaFilhos {get; set;} //works

Binding list of objects to ItemsControl with custom ItemTemplate in MVVM

Current Setup
I have a custom class representing an installer file and some properties about that file, conforming to the following interface
public interface IInstallerObject
{
string FileName { get; set; }
string FileExtension { get; set; }
string Path { get; set; }
int Build { get; set; }
ProductType ProductType { get; set; }
Architecture ArchType { get; set; }
bool Configurable { get; set; }
int AverageInstallTime { get; set; }
bool IsSelected { get; set; }
}
My ViewModel has a ReadOnlyObservableCollection<IInstallerObject> property named AvailableInstallerObjects.
My View has a GroupBox containing the ItemsControl which binds to the aforementioned property.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
The binding works correctly, except it's not user friendly. 100+ items are shown.
Need Help Here
I'd like to be able to use my collection of IInstallerObjects but have the View present them with the following ItemTemplate structure.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=ProductType}" Margin="5" />
<ComboBox ItemsSource="{Binding Path=Build}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
Basically I want to be able to group by the ProductType property, showing a list of the available products, with the ComboBox representing the available Build property values for IInstallerObjects of the ProductType.
I can use LINQ in the ViewModel to extract the groupings, but I have no idea how I'd bind to what I've extracted.
My research also turned up the possibility of using a CollectionViewSource but I'm not certain on how I can apply that to my current setup.
I appreciate your help in advance. I'm willing to learn so if I've overlooked something obvious please direct me to the information and I'll gladly educate myself.
If Build should be a collection type.
so your class should be structured like this as an example.
Public Class Customer
Public Property FirstName as string
Public Property LastName as string
Public Property CustomerOrders as observableCollection(OF Orders)
End Class
This should give you the expected results. Each item in the main items presenter will show first name last name and combobox bound to that customers orders.
I know it's simple but this should do.
All you have to do is declare a CollectionViewSource in your view and bind it to the ObservableCollection. Within this object you declare one or more GroupDescriptions which will split up the source into several groups.
Bind this source to the listbox, create a Template for the group description and you are done.
An example can be found here: WPF Sample Series – ListBox Grouping, Sorting, Subtotals and Collapsible Regions. More about CollectionViewSource can be found here: WPF’s CollectionViewSource
The description of your problem lead me to believe you are looking for some kind of colapsing / expanding / grouped / tree-view sort of thing.
XAML for the tree-view
<Window x:Class="WPFLab12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:WPFLab12"
Title="MainWindow" Height="350" Width="525">
<Grid>
<GroupBox Header="Products">
<TreeView ItemsSource="{Binding Path=ProductTypes}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type loc:ProductType}"
ItemsSource="{Binding AvailableInstallerObjects}">
<TextBlock Text="{Binding Description}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:InstallerObject}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</GroupBox>
</Grid>
</Window>
What does that do? Well, it establishes a hierarchy of controls in the tree based on the type of data found. The first HierarchicalDataTemplate handles how to display the data for each class, and how they are related in the hierarchy. The second HierarchicalDataTemplate handles how to display each InstallerObject.
Code behind for the Main Window:
public partial class MainWindow : Window
{
public ReadOnlyObservableCollection<ProductType> ProductTypes
{
get { return (ReadOnlyObservableCollection<ProductType>)GetValue(ProductTypesProperty); }
set { SetValue(ProductTypesProperty, value); }
}
// Using a DependencyProperty as the backing store for ProductTypes. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ProductTypesProperty =
DependencyProperty.Register("ProductTypes", typeof(ReadOnlyObservableCollection<ProductType>), typeof(MainWindow), new UIPropertyMetadata(null));
public MainWindow()
{
this.InitializeComponent();
this.ProductTypes = new ReadOnlyObservableCollection<ProductType>(
new ObservableCollection<ProductType>()
{
new ProductType()
{
Description = "Type A",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "B" },
new InstallerObject() { FileName = "C" },
})
},
new ProductType()
{
Description = "Type B",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "D" },
})
}
});
this.DataContext = this;
}
}
This is totally cheating, though - normally the MainWindow.cs would not serve as the DataContext and have all this stuff. But for this example I just had it make a list of ProductTypes and populate each ProductType class with the InstallerObject instances.
Classes I used, note I made some assumptions and modified your class to suit this View Model better:
public class InstallerObject
{
public string FileName { get; set; }
public string FileExtension { get; set; }
public string Path { get; set; }
public int Build { get; set; }
public bool Configurable { get; set; }
public int AverageInstallTime { get; set; }
public bool IsSelected { get; set; }
}
public class ProductType
{
public string Description { get; set; }
public ReadOnlyObservableCollection<InstallerObject> AvailableInstallerObjects
{
get;
set;
}
public override string ToString()
{
return this.Description;
}
}
So, in MVVM, it seems to me that your current InstallerObject class is more of a Model layer sort of thing. You might consider transforming it in your ViewModel to a set of collection classes that are easier to manage in your View. The idea in the ViewModel is to model things similarly to how they are going to be viewed and interracted with. Transform your flat list of InstallerObjects to a new collection of hierarchical data for easier binding to the View.
More info on various ways to use and customize your TreeView: http://www.codeproject.com/Articles/124644/Basic-Understanding-of-Tree-View-in-WPF

Categories

Resources