Showing a single value from api call TextBlock (MVVM) - c#

I have been stuck on this for a few hours now. I am using the IGDB api for the application I'm building. So, I'm trying to get the ListBox I have to show the Game Title, cover and Developer. I've managed to get all of them to show up in the list with no issues but the main issue comes in when I just want to show a single developer within the list, the list is showing all companies involved plus the developer because the API returns an array of Involved Companies and within those Involved Companies the developer body returns a Boolean value to say which one is the developer. I can see it in my head, if developer is true then show the developer and remove the other involved companies. Here is an image of what is showing up when I run my query:
Query Image
I have outlined it in red. From that list all I would need is Bungie since they're the developers. So in my view I have a ListBox and a ListView nested within it:
BrowseGamesView
<ListBox ItemsSource="{Binding Games}"
Width="390"
Height="500"
Grid.Row="4">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
Margin="0 0 10 0">
<Image Source="{Binding cover.image_id, Converter={StaticResource stringToImage}}"
Stretch="Fill"
Height="70"
Width="60"/>
</Border>
<TextBlock Text="{Binding name}"
FontWeight="Bold"
Grid.Column="1"
Grid.Row="0"/>
<ListView Grid.Column="1"
Grid.Row="1"
ItemsSource="{Binding involved_companies}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding company.name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Within my ViewModel I have a method for making the query and also holds the ObservableCollection (Games).
BrowseGamesViewModel
public ObservableCollection<Game> Games { get; set; }
public async void MakeQuery()
{
var games = await IGDBHelper.GetGames(Query);
Games.Clear();
foreach(var game in games)
{
Games.Add(game);
}
}
In my Helper class I get the games
IGDBHelper
public static async Task<List<Game>> GetGames(string query)
{
List<Game> games = new List<Game>();
string myJson = "search \"{0}\"; fields name, cover.url, cover.image_id, involved_companies.developer, involved_companies.company.name; limit 500;";
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("user-key", "key hidden");
var response = await client.PostAsync(BASE_URL_GAMES, new StringContent(string.Format(myJson, query), Encoding.UTF8, "application/json"));
var responseString = await response.Content.ReadAsStringAsync();
games = JsonConvert.DeserializeObject<List<Game>>(responseString);
}
return games;
}
And all of models are concrete classes for the Json
public class Game
{
public int id { get; set; }
public Cover cover { get; set; }
public List<InvolvedCompany> involved_companies { get; set; }
public string name { get; set; }
}
public class InvolvedCompany
{
public int id { get; set; }
public Company company { get; set; }
public bool developer { get; set; }
}
public class Company
{
public int id { get; set; }
public string name { get; set; }
}
public class Cover
{
public int id { get; set; }
public string image_id { get; set; }
public string url { get; set; }
}
So, to reiterate, I need to somehow just show the developers name by removing the rest of the involved companies from the list. I have tried several things from converters to trying it in the converter class and each time I get exceptions for null.

I would add a new readonly property to the Game class:
public List<InvolvedCompany> DeveloperCompanies
{
get
{
return involved_companies.Where(c => c.developer == true).ToList();
}
}
And then you can bind this new property to your ListView:
<ListView Grid.Column="1"
Grid.Row="1"
ItemsSource="{Binding DeveloperCompanies}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding company.name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This way you would be showing only the developer companies but you would keep the information about all the involved companies as well, if you need to use them for another purpose.
I also suggest you to use PascalCase for the public members of a class. So, for example involved_companies would become InvolvedCompanies.

First, I would suggest to name your properties in PascalCase.
Now for your problem, I suggest to filter the companies by developer property and assign a new list of involving companies to the game before adding it to the list.
foreach(var game in games)
{
game.involved_companies = game.involved_companies.Where(e => e.developer == true).Tolist();
Games.Add(game);
}
Not tested but I believe this should work.

Related

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.

Xamarin Forms listview databinding

I am currently following this guide:
http://www.morganskinner.com/2015/01/xamarin-forms-contacts-search.html
I am willing to make a contacts page in my app, like the one showed in the pictures there, still something is not working out as it should for me:
I wrote/copied the XAML:
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<SearchBar x:Name="search" Placeholder="Search"/>
<ListView x:Name="ProvaView" Grid.Row="1" ItemsSource="{Binding FilteredContacts}" IsGroupingEnabled="true" GroupDisplayBinding="{Binding Key}" GroupShortNameBinding="{Binding Key}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Name}" TextColor="Black" Detail="{Binding PhoneNumber}" DetailColor="Gray">
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
and now I am trying to "display something", not dinamically loaded (yet), from a list of data.
I declared a class like this:
public class Contact
{
public string Name { get; set; }
public string PhoneNumber { get; set; }
}
}
and now the code behind the xaml is this:
public partial class ContactsPage : MasterDetailPage
{
public List<Contact> FilteredContacts = new List<Contact>
{
new Contact
{
Name = "Guido",
PhoneNumber = "3292773477"
},
new Contact
{
Name = "Luca",
PhoneNumber = "3472737445"
},
new Contact
{
Name = "Luca",
PhoneNumber = "3472737445"
}
};
public ContactsPage()
{
InitializeComponent();
ProvaView.ItemsSource = FilteredContacts;
}
}
When I launch the app, all I get anyway is an empty listview, with 3 fields but nothing in it.
Where am I doing wrong?
Also, straight from the page where I am working off, I can't understand the meaning of these Fields associated to the listview:
GroupDisplayBinding="{Binding Key}"
GroupShortNameBinding="{Binding Key}"
and as much as I could look around on the web, I coudln't find any reference to both of them.
Most likely the problem is with the way you declared FilteredContacts, it should be a property, and not a field. The reflection mechanism used in binding only searches for properties, fields are not included.

How do you bind to a complex object in usercontrol.resources for Windows Phone 8.1 (C#)

I have searched the web for the last few days but can't seem to find something that I would have thought was quite a simple task. I would like to add a resource in my XAML page of my windows phone application which will reference a complex object but I can't find the correct method. Is this possible? Object is made up something similar to:
Public class ComplexClass
{
Public string name { get; set; }
Public int ID { get; set; }
Public observablecollection<SimpleClass> simpleObjects { get; set; }
Public addSimpleObject(SimpleClass newSimpleObject)
{
if (simpleObjects == null)
simpleObjects = new ObservableCollection<SimpleClass>();
simpleObjects.Add(newSimpleObject);
}
}
Public Class SimpleClass
{
Public String Name { get; set; }
Public String Disc { get; set; }
}
You could use MVVM do achieve this. There are already heaps of tutorials available that you can access to show you how to follow this design pattern, so I won't go into that.
Instead I'll just show you a simple way of getting the data to your view.
In the constructor of your UserControl (or Page or whatever), set up the DataContext to an instance of your ComplexClass:
ComplexClass complexClass;
public MyUserControl1()
{
complexClass = new ComplexClass();
complexClass.AddSimpleObject(new SimpleClass { Name = "Bob" });
this.DataContext = complexClass;
this.InitializeComponent();
}
Then in your XAML you can bind to it like this:
<StackPanel>
<!-- Binding to properties on ComplexClass -->
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding ID}" />
<ListView ItemsSource="{Binding SimpleObjects}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- Binding to properties on SimpleClass -->
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Disc}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
Without knowing specifics of your code, it's hard for me to suggest a method that is most suitable for you. I'd read up on MVVM and view models.

Cannot apply Click event to the button with the custom button style

Following is the structure of my data:
public class TokenItems
{
public int ID { get; set; }
public int itemID { get; set; }
public string name { get; set; }
public int qty { get; set; }
public int twQty { get; set; }
public bool readyStatus { get; set; }
public Nullable<DateTime> orderOn { get; set; }
public int styleType { get; set; }
}
public class Token
{
public int tokenNo { get; set; }
public Nullable<DateTime> startedOn { get; set; }
public List<TokenItems> tokenItems { get; set; }
public bool readyStatus { get; set; }
public bool acceptStatus { get; set; }
}
The above structure fits well in the following DataTemplate. (It has multiple data template, DataTemplate within the DataTemplate)
TokenPanel has the data from Token class which is assigned from code behind like this:
TokenPanel.ItemsSource = List<Token> filledList;
tokenItems is assigned within the XAML:
ItemsSource = {Binding List<TokenItems> tokenItem}
The Template of tokenItems further contains the template of buttons which are created from the list items of the tokenItem.
I have applied custom style (redButton) to the button with Click event (listClick).
<ScrollViewer>
<ItemsControl x:Name="TokenPanel">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.119*"/>
<RowDefinition Height="0.881*"/>
</Grid.RowDefinitions>
<TextBlock TextWrapping="Wrap" Text="{Binding tokenNo}" />
<StackPanel Grid.Row="1" Width="Auto" HorizontalAlignment="Stretch">
<ItemsControl Height="Auto" ItemsSource="{Binding tokenItems}" HorizontalAlignment="Stretch" Width="Auto">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Height="38" Width="Auto" Style="{StaticResource redButton}" HorizontalContentAlignment="Stretch" Click="listClick" >
<TextBlock Text= "{Binding name}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Height="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" x:Name="tokenListBox" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
The program compiles well without any error. However when I update the TokenList it shows error with NullReference exception somewhere in XAML parsing.
I tried removing the Click event property from the XAML without any click event assigned. This executed program very well. But I can't click on the button which is the most thing I want. Further, I need custom style as well.
I am unable to figure out what the problem is... A quick fix will be highly appreciated.
EDIT:
This updates the tokenList;
public void update() {
List<TokenItems> tempList = new List<TokenItems>();
tokenList.Clear(); // clears previous items in tokenList;
while(database.Read()) {
tempList.Add(new Token() {
item= (int)database["item"]
});
}
var temp = new List<BumpBar.TokenItems>();
temp.AddRange(tokenModify(tempItems)); // modifies the items within the list
tempItems.Clear(); // clear to refill the tempItems
tempItems.AddRange(temp);
tokenList.Add(new Token() {
tokenNo = tokensList[i].tokenNo,
tokenItems = tempItems,
startedOn = tokensList[i].startedOn
});
}
Post the code that relates to this comment. I'm guessing you're accidentally nullify that token list.
The program compiles well without any error. However when I update the TokenList it shows
error with NullReference exception somewhere in XAML parsing.

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