Bind List<Object> to a comboxbox in c# wpf - c#

I have a static class named Building which contains a List<Beam> Beams as its property;
public static class Building
{
public static readonly List<Beam> Beams = new List<Beam>();
}
public class Beam
{
public string Story;
public double Elevation;
}
I'm trying to Bind the Building.Beams to a combobox in XAML so that Elevation and Story properties of each item in Building.Beams list is displayed in different columns in the combobox. I have been able to implement the two columns, I just can't Bind these properties.
Here is what I have tried so far:
<ComboBox x:Name="cmbBuilding" ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Width="300">
<TextBlock Width="150" Text="{Binding Path=Story }"/>
<TextBlock Width="150" Text="{Binding Path=Elevation}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
var b1 = new Beam { Elevation = 320, Story = "ST1" };
var b2 = new Beam { Elevation = 640, Story = "ST2" };
Building.Beams.Add(b1);
Building.Beams.Add(b2);

First of all you can't bind with fields.
Convert Story and Elevation to properties (automatic properties in your case will do)
public class Beam
{
public string Story { get; set;}
public double Elevation { get; set;}
}
Second, you should use ObservableCollection in case you are adding items to the list after loading finishes so that UI gets notification.
public static readonly ObservableCollection<Beam> Beams
= new ObservableCollection<Beam>();

Try this example:
XAML
<Grid>
<ComboBox x:Name="cmbBuilding" Width="100" Height="25" ItemsSource="{Binding Path=Beams}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Width="300">
<TextBlock Width="150" Text="{Binding Path=Story}" HorizontalAlignment="Left" />
<TextBlock Width="150" Text="{Binding Path=Elevation}" HorizontalAlignment="Right" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Add item" VerticalAlignment="Top" Click="Button_Click" />
</Grid>
Code-behind
public partial class MainWindow : Window
{
Building building = new Building();
public MainWindow()
{
InitializeComponent();
building.Beams = new List<Beam>();
building.Beams.Add(new Beam
{
Elevation = 320,
Story = "ST1"
});
this.DataContext = building;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var b1 = new Beam { Elevation = 320, Story = "ST1" };
var b2 = new Beam { Elevation = 640, Story = "ST2" };
building.Beams.Add(b1);
building.Beams.Add(b2);
cmbBuilding.Items.Refresh();
}
}
public class Building
{
public List<Beam> Beams
{
get;
set;
}
}
public class Beam
{
public string Story
{
get;
set;
}
public double Elevation
{
get;
set;
}
}
Some notes
When you use properties in the Binding, you need to be properties with get and set, not fields.
Properties, what were added to the List<T> will automatically update, you should call MyComboBox.Items.Refresh() method, or use ObservableCollection<T>:
ObservableCollection represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.

Is it maybe because you have declared Beams as readonly yet you try to ADD items to it? Beams is also defined as a variable, try removing the readonly and making it a property with a getter and setter

Related

How to Bind an Observablecollection to a GridView?

I am working on an App that selects Images and adds them to an Observablecollection. I want to show this collection in Xaml. There is no clear understandable answer that I have found so far.
in the MainPage:
public ObservableCollection<TimerWallpaper> timerWallpapers = new ObservableCollection<TimerWallpaper>();
And then the code for its class is this:
public class TimerWallpaper
{
public string timerFileName;
public BitmapImage timerImgSource;
public string timerTime;
public TimerWallpaper(string name, BitmapImage imgSource, int hours, int mins)
{
this.timerFileName = name;
this.timerImgSource = imgSource;
this.timerTime = hours.ToString() + " : " + mins.ToString();
}
}
Till this point it seems as the code is working.. the obstacle is with this code:
<GridView ItemsSource="x:Bind timerWallpapers">
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:TimerWallpaper">
<Image Height="100" Width="100" Source="x:Bind timerImgSource"/>
<TextBlock Text="{x:Bind timerFileName}"/>
<TextBlock Text="{x:Bind timerTime}"/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
I keep getting "Invalid Value" for the elements of the data templet.
What are the requirements to bind the GridView to the collection?
What is the correct format to do so?
OK, there are a number of problems with your code. First off, you should bind to properties, not fields, so your MainPage.cs should look something like this:
public sealed partial class MainPage : Page
{
public ObservableCollection<TimerWallpaper> TimerWallpapers { get; set; }
public MainPage()
{
this.InitializeComponent();
TimerWallpapers = new ObservableCollection<TimerWallpaper>();
DataContext = this;
}
}
and your TimerWallpaper like this:
public class TimerWallpaper
{
public string TimerFileName { get; set; }
public BitmapImage TimerImgSource { get; set; }
public string TimerTime { get; set; }
public TimerWallpaper(string name, BitmapImage imgSource, int hours, int mins)
{
this.TimerFileName = name;
this.TimerImgSource = imgSource;
this.TimerTime = hours.ToString() + " : " + mins.ToString();
}
}
(Or use private set if you want to)
Next, your binding syntax is wrong on a couple of lines where you forgot to enclose it in curly braces, and lastly, the DataTemplate can only have a single child, so you need to wrap your UI elements in a layout, e.g. a StackPanel, like so:
<GridView ItemsSource="{x:Bind TimerWallpapers}">
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:TimerWallpaper">
<StackPanel>
<Image Height="100" Width="100" Source="{x:Bind TimerImgSource}"/>
<TextBlock Text="{x:Bind TimerFileName}"/>
<TextBlock Text="{x:Bind TimerTime}"/>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>

WPF MVVM creating multiple datagrids and bind items to them

I have met following problem in WPF binding.
I need to load objects from XML file and create list of loaded items in listbox, and when listbox item is selected then display suitable set of objects.
I can do it in 'code behind' style, but I really want to do it in proper MVVM way.
My Matrixes class is generated by xsd2code from which contains:
List<CorrectionMatrixType> correctionMatrixField;
and follows
public partial class CorrectionMatrixType {
public MatrixType A {get; set;}
public MatrixType B {get; set;}
public MatrixType C {get; set;}
... }
How can I create 'dynamically'something like Grid with three DataGrids by Viewmodel and bind each matrix (A,B,C) to them which content will change depends of value selected in listbox? I know that to bind my MatrixType to DataGrid i have to use ValueConverter to convert my object to two-dimensional array.
Maybe I have to admit I am using MVVM Light.
Please, any suggestions?
I would use the INotifyPropertyChanged Interface. Here is a small example (not exactly your case, but enough to show the principle, I think):
MatrixType class:
public class MatrixType
{
public string Name { get; set; }
public string Width { get; set; }
public string Height { get; set; }
}
Xaml:
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Items}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedItem}"></ListBox>
<Grid Grid.Column="1">
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding SelectedItem.Name}" Height="30"/>
<TextBox Text="{Binding SelectedItem.Height}" Height="30"/>
<TextBox Text="{Binding SelectedItem.Width}" Height="30"/>
</StackPanel>
</Grid>
</Grid>
MainViewModel.cs:
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
var list = new List<MatrixType>
{
new MatrixType {Height = "233", Name = "A", Width = "133"},
new MatrixType {Height = "333", Name = "B", Width = "233"},
new MatrixType {Height = "433", Name = "C", Width = "333"}
};
Items = new ObservableCollection<MatrixType>(list);
}
private MatrixType _selectedItem;
public MatrixType SelectedItem
{
get => _selectedItem;
set { _selectedItem = value; OnPropertyChanged(); }
}
public ObservableCollection<MatrixType> Items { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainViewModel.cs (when using MVVM Light):
public class MainViewModel : ObservableObject
{
public MainViewModel()
{
var list = new List<MatrixType>
{
new MatrixType {Height = "233", Name = "A", Width = "133"},
new MatrixType {Height = "333", Name = "B", Width = "233"},
new MatrixType {Height = "433", Name = "C", Width = "333"}
};
Items = new ObservableCollection<MatrixType>(list);
}
private MatrixType _selectedItem;
public MatrixType SelectedItem
{
get => _selectedItem;
set { _selectedItem = value; RaisePropertyChanged(); }
}
public ObservableCollection<MatrixType> Items { get; set; }
}
I wrote solution by myself, I don't know if it is good MVVM solution.
I re-write my XSD so MatrixType becomes SimpleMatrix, and now:
XAML:
<ListBox Margin="5,20,0,5" ItemsSource="{Binding CorrectionMatrixes}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<command:EventToCommand Command="{Binding SelectionChangedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
<i:EventTrigger EventName="Loaded">
<command:EventToCommand Command="{Binding ListBoxLoadedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
<DataGrid CanUserAddRows="False" HeadersVisibility="None" ItemsSource="{Binding CorrectionMatrixA, Converter={StaticResource MatrixToArray }}"/>
And in my viewmodel:
public RelayCommand<SelectionChangedEventArgs> SelectionChangedCommand => new RelayCommand<SelectionChangedEventArgs>(SelectionChanged);
public RelayCommand<RoutedEventArgs> ListBoxLoadedCommand => new RelayCommand<RoutedEventArgs>(ListBoxLoaded);
private string CorrectionMatrixName { get; set; }
private void ListBoxLoaded(RoutedEventArgs obj)
{
if (obj.Source is ListBox listBox)
{
listBox.SelectedIndex = 0;
}
}
private void SelectionChanged(SelectionChangedEventArgs obj)
{
if (obj.AddedItems.Count <= 0) return;
if (obj.AddedItems[0] is CorrectionMatrix matrix)
{
CorrectionMatrixName = matrix.name;
}
RaisePropertyChanged(() => CorrectionMatrixA);
}
public SimpleMatrix CorrectionMatrixA
{
get
{
try
{
var x = Matrixes.Correction.Where(a => a.name == CorrectionMatrixName)
.Select(a => a.A).Single();
return x;
}
catch (InvalidOperationException)
{
return null;
}
}
}
Matrixes are loaded by:
Matrixes = settingsLoader.LoadMatrixes(Properties.Resources.MatrixesSettings);
How does it all works:
when user control is loaded => selected index on listbox is setting to zero
When selected item on listbox is changed it fires event that changes CorrectionMatrixName
Binding properties returns suitable matrix finding it in array by name
I don't post Converter code - it doesn't matter here.
Thats full, my own solution that worked for me. I hope it will helps other people

How to bind dictionary with bitmap to combobox

I've a problem binding a Dictionary<string, Bitmap> to my combobox.
The Bitmaps are saved in the resource file.
This could loads the items in the combobox:
ComboBoxLanguage.ItemsSource = Languages;
ComboBoxLanguage.DisplayMemberPath = "Value";
ComboBoxLanguage.SelectedValuePath = "Key";
ComboBoxLanguage.SelectedValue = Settings.Default.language;
This is my dictionary:
Languages = new Dictionary<string, Bitmap>
{
{ "en-US", Properties.Resources.US},
{"de-DE", Properties.Resources.DE}
};
But my ComboBox only shows Sysytem.Drawing.Bitmap
Can somebody help me?
Probably you need to use ObservableCollection and make wrapper class.
public class ComboBoxData
{
public string Path { get; set; }
public string Text { get; set; }
}
In view model you should specify a list of combobox elements.
public ObservableCollection<ComboBoxData> Languages { get; set; }
public View()
{
InitializeComponent();
this.Languages = new ObservableCollection<ComboBoxData>()
{
new MyComboboxData(){Path = "Image1.jpg", Text = "Text1"},
new MyComboboxData(){Path = "Image2.jpg", Text = "Text2"}
};
this.DataContext = this;
}
And in xaml bind your combobox to this collection.
<ComboBox Name="ComboBoxLanguage" ItemsSource="{Binding Languages}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

binding events to wpf list for realtime update

I am building a WPF app that will populate filtered headlines from a variety of news services. Each headline triggers an event, which in a console app I can display on the console. I want to use WPF here but have bot used it prior to this endeavor. My mainwindow xaml is as shown below. My original thought was to have an ObservableCollection populate list items in a listview in the xaml. If that is not the right approach, I'm open to expert opinion on a better way as speed of receipt to display is vital. If what I am doing is proper then how do I bind a new entry to the ObservableCollection to a new list item to display?
<StackPanel Orientation="Vertical" Margin="5,150 5 50" Name="HeadlinePanel">
<TextBlock Text="Filtered Headlines From Monitoring List"
HorizontalAlignment="Left" Margin="0,0 5 5" Name="ScrollingHeadlineLabel" FontWeight="Bold" FontSize="14" Background="LightSkyBlue" />
<ListBox>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="a property on the headline" />
<TextBlock><Run Text="headline is from a website"/></TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="a property on the headline" />
<TextBlock><Run Text="headline is from TWTR"/></TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="a property on the headline" />
<TextBlock><Run Text="headline from a different website"/></TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="a property on the headline" />
<TextBlock><Run Text="text from a different tweet"/></TextBlock>
</StackPanel>
</ListBoxItem>
</ListBox>
</StackPanel>
In the console app the streaming begins (code shown below) in the filteredStream.Start() but the handler needs to register prior. In the console app I can write to the console (commented out) but here I add the headline object to the collection when the event fires. My question is how to bind that to my xaml list items. I will initiate the stream from mainwindow method? or some method I create to run within that?
var config = new TwitterOAuthConfig()
{
ConsumerKey = customerKey,
ConsumerSecret = customerSecret,
AccessToken = accessToken,
AccessTokenSecret = accessTokenSecret,
GeoOnly = false,
KeywordsToMonitor = keywords,
UsersToFollow = followers
};
var filteredStream = new TwitterClient(config);
var headlineCollection = new ObservableCollection<Headline>();
// subscribe to the event handler
filteredStream.HeadlineReceivedEvent +=
(sender, arguments) => headlineCollection.Add(arguments.Headline);
//Console.WriteLine("ID: {0} said {1}", arguments.Headline.Username, arguments.Headline.HeadlineText);
filteredStream.ExceptionReceived += (sender, exception) => Console.WriteLine(exception.HeadlineException.ResponseMessage);
filteredStream.Start();
Here is my Original HeadlineViewModel
public class HeadlineViewModel : ObservableItem
{
private string _headlineText;
public string Source { get; set; }
public string Username { get; set; }
public string Text
{
get { return _headlineText; }
set
{
_headlineText = value;
RaisePropertyChangedEvent("HeadlineText");
}
}
public List<string> UrlsParsedFromText { get; set; }
public string TimeStamp { get; set; }
}
I've updated it to the following:
public class HeadlineViewModel
{
public class HeadlineDisplayItems: ObservableItem
{
private string _headlineText;
public string HeadlineIconPath { get; set; }
public string TimeStamp { get; set; }
public string Username { get; set; }
public string Text
{
get { return _headlineText; }
set
{
_headlineText = value;
RaisePropertyChangedEvent("HeadlineText");
}
}
}
public List<string> UrlsParsedFromText { get; set; }
public ObservableCollection<HeadlineDisplayItems> HeadlineCollection { get; set; }
}
I don't know about your architecture, but wpf is mostly used with what they call MVVM (Model-View-ViewModel) where you have your View (you already posted the code), the ViewModel (I believe you don't have one) and the model (that is the Headline you are using). The objective of the ViewModel is to simplify the life of the view and make available all the information and actions it needs to display.
For example, you should hava a ViewModel for the whole view you are building, let's say "HeadlinePanelViewModel" (I don't recommend panel in the name because the idea of using a ViewModel is to abstract the controls or technologies being used). The HeadlinePanelViewModel needs to make the headlines available, so it must have a collection of a ViewModel representing all the information concerned to the headline (icons, titles, links, ...). In the end, you have an HeadlinePanelViewModel which contains an ObservableCollection. Set this as DataContext of your View and you must be ready to go to display your info.
Now comes the part of actually loading the info. Again, I don't know about your architecture. But in VERY simple terms, you could instantiate the filteredStream inside of your HeadlinePanelViewModel and everytime an HeadlineReceivedEvent is fired, you create an HeadlineViewModel corresponding to it and add to your collection.
"Complete" code based in the code in your answer:
The ViewModel:
public class HeadlineViewModel
{
public HeadlineViewModel()
{
// This is here only for simplicity. Put elsewhere
var config = new TwitterOAuthConfig()
{
ConsumerKey = customerKey,
ConsumerSecret = customerSecret,
AccessToken = accessToken,
AccessTokenSecret = accessTokenSecret,
GeoOnly = false,
KeywordsToMonitor = keywords,
UsersToFollow = followers
};
var filteredStream = new TwitterClient(config);
HeadlineCollection = new ObservableCollection<HeadlineDisplayItems>();
// subscribe to the event handler
filteredStream.HeadlineReceivedEvent +=
(sender, arguments) => HeadlineCollection.Add(ConvertToViewModel(arguments.Headline));
//Console.WriteLine("ID: {0} said {1}", arguments.Headline.Username, arguments.Headline.HeadlineText);
filteredStream.ExceptionReceived += (sender, exception) => Console.WriteLine(exception.HeadlineException.ResponseMessage);
filteredStream.Start();
}
private HeadlineDisplayItems ConvertToViewModel(Headline headline)
{
// Conversion code here
}
public class HeadlineDisplayItems: ObservableItem
{
private string _headlineText;
public string HeadlineIconPath { get; set; }
public string TimeStamp { get; set; }
public string Username { get; set; }
public string Text
{
get { return _headlineText; }
set
{
_headlineText = value;
RaisePropertyChangedEvent("HeadlineText");
}
}
}
public List<string> UrlsParsedFromText { get; set; }
public ObservableCollection<HeadlineDisplayItems> HeadlineCollection { get; set; }
}
The View:
<StackPanel Orientation="Vertical" Margin="5,150 5 50" Name="HeadlinePanel">
<TextBlock Text="Filtered Headlines From Monitoring List"
HorizontalAlignment="Left" Margin="0,0 5 5" Name="ScrollingHeadlineLabel" FontWeight="Bold" FontSize="14" Background="LightSkyBlue" />
<ListBox ItemsSource="{Binding HeadlineCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding HeadlineIconPath}" />
<TextBlock><Run Text="{Binding Text}"/></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
The code missing is where you do the this.DataContext = new HeadlineViewModel(); to the View.
EDIT: You may experience some problems with cross-thread operations if you try to update the observableCollection from a thread different of the view thread. A workaround is to use the solution in this link, but I don't think it's the best approach.
Create your ObservableCollection as a Property that you can Reference in XAML. Either create it directly in your MainWindow-Class or instantiate your collection as a StaticResource.
Bind your ObservableCollection as ItemsSource to your Listbox
<ListBox ItemsSource="{Binding Path=HeadlineCollection}"></ListBox>
and use an DataTemplate to bind your data to it
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image ... />
<TextBlock Text="{Binding Path=Text}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
For the Headline, create a data class that manages what you need to display (headline, icons, etc.). Something like this:
class Headline
{
bool isTwitter {get; set;}
string Text {get; set;}
}
Then in your client object you can simply add a new object to the ObservableCollection by calling the Add()-Method and the Application will automatically render the new object.
You can start your query client on the main UI thread but for a responsive UI you should let the query routine run in it's own thread (e.g. by using a BackgroundWorker) so that the UI isn't cluttered by it.

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