I am new to WPF so please accept my apologies if my question is stupid.
I am creating a food ordering system which consists of 2 main sections, the "Food Menu" and the "Order List". When user chooses an item from the food menu, it will be added to the a listbox control which represents the order list.
I have created a few custom objects: Order, OrderLine, Item, as well as a Collection class, OrderLineCollection, for "OrderLine". They look like the following:
public class Order
{
public string ID { get; set; }
public DateTime DateTime { get; set; }
public double TotalAmt { get; set; }
public OrderLineCollection LineItems { get; set; }
}
public class OrderLine
{
public Item Item { get; set; }
public double UnitPrice { get; set; }
public int Quantity { get; set; }
public double SubTotal { get { return unitPrice * quantity; } }
}
[Serializable]
public class OrderLineCollection : CollectionBase
{
public OrderLine this[int index]
{
get { return (OrderLine)List[index]; }
set { List[index] = value; }
}
}
public class Item
{
public string ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double UnitPrice { get; set; }
public byte[] Image { get; set; }
}
My ListBox control has a DataTemplate so that more details are shown for each item. The XAML as below:
<Page x:Class="FoodOrdering.OrderList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Order List">
<ListBox Name="lbxOrder" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Text="{Binding item.name}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="x "/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding quantity}" Margin="10,0,0,0"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding subTotal, Converter={StaticResource priceConverter}}" HorizontalAlignment="Right"/>
<Button Grid.Row="1" Grid.Column="2" Margin="0,5,0,0" Click="btnDelete_Click">Delete</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Page>
so when items are added, the ListBox will look like the below image:
https://dl.dropbox.com/u/6322828/orderList.png
In the code-behind, I have created a static variable currentOrder for storing the current order and it will be used by other classes/methods.
And every time its value is changed, the following stupid method LoadCurrentOrder() is called to refresh the ListBox view.
public partial class OrderList : Page
{
public static Order currentOrder = new Order();
public OrderList()
{
InitializeComponent();
LoadCurrentOrder();
}
public void LoadCurrentOrder()
{
lbxOrder.Items.Clear();
foreach (OrderLine ol in currentOrder.LineItems)
lbxOrder.Items.Add(ol);
}
}
My problem is how can I bind the data in an elegant way (such as using Resources ItemsSource and so on) instead of using the above method, so that the ListBox will update automatically every time the value of the variable is changed?
I tried
lbxOrder.ItemsSource = currentOrder;
but it does not work as currentOrder is not a System.Collections.IEnumerable object.
1 - Don't create your own collection types, the .Net framework Base Class Library already has pretty much everything you need.
Remove the OrderLineCollection and use an ObservableCollection<OrderLine>.
2 - Try to stick to MVVM conventions. This means you should not manipulate UI elements in code.
public OrderList()
{
InitializeComponent();
LoadCurrentOrder();
DataContext = this;
}
then in XAML:
<ListBox ItemsSource="{Binding LineItems}" HorizontalContentAlignment="Stretch">
3 - Do not name UI elements in XAML unless you need to reference them in XAML. This will help you reconsider your approach every time you find yourself wanting to manipulate UI elements in procedural code.
4 - Get used to the C# naming convention. Property names should be "proper cased" (I.E LineItems instead of lineItems)
All you have to do is to make the lineItems property into an ObservableCollection<yourType>. Then when this collection is changed (items added, removed) the listbox will refresh automatically.
If your problem is that the order itself changes and you need to refresh the listbox. Then all you need to do is to implement INotifyPropertyChanged and when the order changes, to trigger the notification that the property has changed and the UI will refresh via the binding.
As for the binding in a more elegant way. You can bind to the currentOrder.lineItems:
lbxOrder.ItemsSource = currentOrder.lineItems;//this should be an observable collection if you intend to have items change
Related
I'm having a problem setting the Binding/Path property in my XAML.
I know this ComboBox's ItemSource property is updating properly, since I get a bunch of empty text-boxes when I update the viewmodel (instead of textboxes with text, which is what I expect).
So I believe the Binding in the DataTemplate section of my ComboBox needs a different binding path, but I'm not sure what to set the binding path to.
<ComboBox ItemsSource="{Binding Path=Locations}" Visibility="{Binding SettingsOptionsVisibility}" Grid.Column="0" x:Name="locationCB" VerticalAlignment="Top" SelectionChanged="locationCB_SelectionChanged" HorizontalAlignment="Left" Width="350" Height="30" IsHitTestVisible="False" IsEnabled="False" Focusable="False">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Here's a picture of my LocationCB's ItemsSource in the Watch List in my Codebehind: http://imgur.com/x4SYWER
As expected, my combobox is populated with 8 (textless) elements. What do I need to do to get my binding the text to Name to connect up?
EDIT: code for the Locations object in the ViewModel:
public ObservableCollection<Location> Locations { get; set; }
And code for the Location class wrapped in the observable collection, as requested:
public class Location
{
public Guid LocationID;
public Guid ParentID;
public String Name;
public bool isValid;
}
Change the fields of your location object to properties:
public class Location
{
public Guid LocationID { get; set; }
public Guid ParentID { get; set; }
public String Name { get; set; }
public bool isValid { get; set; }
}
You should be binding to properties and not variables. Instead of
public string Name;
You should change the above to
public string Name {get;set;}
Even better, can implement the INotifyPropertyChanged..
I am new to WPF and wondering, how I could bind multiple and different Controls to a ItemControl in a C# WPF Application?
With my current code I can just put labels and textboxes into my ItemControl. Because of the template definition in my XAML.
But I like to be flexible to put labels and other Controls like combobox, datepicker, etc. into my ItemControl...
How could I do this?
I attached my current XAML Code with a Data Template and the code behind. And here a small picture of the current look and my target what I want:
XAML-CODE:
<Window x:Class="WPFFormTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="400">
<Grid>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="icFields">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="FieldTypes" Margin="10,10,10,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Content="{Binding FieldName}" />
<TextBox Grid.Column="1" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
C#-Code:
namespace WPFFormTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Fields> fields = new List<Fields>();
fields.Add(new Fields() { FieldName = "test1", FieldValue = "1" });
fields.Add(new Fields() { FieldName = "test2", FieldValue = "2" });
fields.Add(new Fields() { FieldName = "test3", FieldValue = "3" });
icFields.ItemsSource = fields;
}
}
public class Fields
{
public string FieldName { get; set; }
public string FieldValue { get; set; }
public string FieldType { get; set; }
}
}
One Approach could be: add all needed controls to the template and switch the visibility. But some help from the view model is needed:
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ScrollViewer.Resources>
<BooleanToVisibilityConverter x:Key="BoolConverter"/>
</ScrollViewer.Resources>
<ItemsControl x:Name="icFields">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="FieldTypes" Margin="10,10,10,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Content="{Binding FieldName}" />
<TextBox Grid.Column="1" Visibility="{Binding ShowText, Converter={StaticResource BoolConverter}}"/>
<CheckBox Grid.Column="1" Visibility="{Binding ShowCheckBox, Converter={StaticResource BoolConverter}}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
public class Fields
{
public string FieldName
{
get;
set;
}
public string FieldValue
{
get;
set;
}
public bool ShowText
{
get;
set;
}
public bool ShowCheckBox
{
get;
set;
}
private int fieldType;
public int FieldType
{
get
{
return fieldType;
}
set
{
fieldType = value;
ShowText = false;
ShowCheckBox = false;
switch (fieldType)
{
case 0:
ShowText = true;
break;
case 1:
ShowCheckBox = true;
break;
}
}
}
}
You might want to consider using an MVVM framework. I know that Caliburn.Micro has some built-in features to make this sort of thing easy, using actual OO features like polymorphism.
First you would define a separate viewmodel class for each item type:
public class FieldViewModel { }
public class TextViewModel : FieldViewModel { public string Value {get;set;} }
public class DateViewModel : FieldViewModel { public DateTime Value {get;set;} }
Then you can define a UserControl called TextView and another called DateView. When you bind your list of FieldViewModel to the UI, Caliburn.Micro will automatically create the right type of view and bind its properties.
It takes a little bit of work to get Caliburn.Micro up and running (you need to hook up the bootstrapper and create a shell viewmodel, at the very least) so I won't provide a complete code sample here -- I'm just trying to give you a flavor for whether it would solve your problem. For the actual code you need, work your way through their documentation; they do a good job of walking you through everything.
The simplest way to do that is to just provide different DataTemplates for different data types. For example, if you had a Person class, you could define a DataTemplate with Name, Age, and Sex TextBox fields, whereas if you had a Car class, you could define a DataTemplate with Make, Model and Year ComboBox fields:
<DataTemplate DataType="{x:Type DataTypes:Person}">
<!--Define TextBoxes here-->
</DataTemplate>
<DataTemplate DataType="{x:Type DataTypes:Car}">
<!--Define ComboBoxes here-->
</DataTemplate>
By defining these DataTemplates without specifying the x:Key reference values, this means that they will be automatically applied by the Framework, whenever it comes across any instances of these types. Therefore, this will display Person details in TextBoxes:
<ItemsControl ItemsSource="{Binding Persons}" ... />
And this will display Car object details in ComboBoxes:
<ItemsControl ItemsSource="{Binding Cars}" ... />
I'm new to Windows Phone development and I'm currently facing an issue while using the LongListSelector in WP8, and I don't know how to proceed to achieve the result I want.
I use it to display a list of items as usual. The class used contains 5 items, and one of them is a float value. I want to display, in the list header, the sum of all positive float values contained in the list, but I have no idea whatsoever about how to do this.
I tried to bind another variable (result of the sum) specificly to the listheader in addition to the original binding, or to add another item in the class containing the sum result (hence repeated throughout the list in each list item), but it didn't work.
I guess this is a pretty basic fonctionnality (for instance to count and display the number of elements of the list), but I can't figure out how to do this.
EDIT : I thought showing my code wouldn't help, but here it is. (I took away the formatting that wasn't relevant)
XAML
<phone:LongListSelector x:Name="ListeSolde" LayoutMode="List">
<phone:LongListSelector.ListHeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding SommeTotale}" />
</DataTemplate>
</phone:LongListSelector.ListHeaderTemplate>
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Nom}" />
<TextBlock Text="{Binding DerniereConnexion}" />
<TextBlock Text="{Binding Depuis}" />
<TextBlock Text="{Binding Solde}" />
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
Class definition
public class resume
{
public string Nom { get; set; }
public double Solde { get; set; }
public string Depuis { get; set; }
public string DerniereConnexion { get; set; }
public resume(string nom, double solde, string depuis, string derniereconnexion)
{
this.Nom = nom;
this.Solde = solde;
this.Depuis = depuis;
this.DerniereConnexion = derniereconnexion;
}
}
public class total
{
public double Total { get; set; }
public double calculTotal(List<resume> soldes)
{
double total = new double();
foreach (resume solde in soldes)
{
if (solde.Solde > 0)
total += solde.Solde;
}
return total;
}
public total(double Dtotal)
{
this.Total = Dtotal;
}
}
And code behind
public MainPage()
{
InitializeComponent();
List<resume> soldes = new List<resume>();
Donnees MainData = new Donnees();
soldes = MainData.RefreshResume(soldes); // A method that basically add items to the list
total SommeTotale = new total(1);
SommeTotale.Total = SommeTotale.calculTotal(soldes);
ListeSolde.ItemsSource = soldes;
}
This of course doesn't work (as far as the list header is concerned) and this is how I would do it
This is how I managed to bind other data from my ViewModel to the header of the LongListSelector.ListHeader.
<phone:LongListSelector.ListHeaderTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="2" Foreground="DarkViolet"
Text="{Binding ElementName=LayoutRoot, Path=DataContext.AddressBookList.Count}" />
</Grid>
</DataTemplate>
</phone:LongListSelector.ListHeaderTemplate>
The ListHeader property binds directly to the DataContext of the LongListSelector. This is different that the items contained within the LongListSelector. The items contained within it are bound to each item within the ItemSource. One of the best ways to get the ListHeader to display is to create an object that houses the data for the LongListSelector
public class ResumeContainer
{
public double SommeTotale { get { return Resumes.Sum(r => r.Value); } }
public IEnumerable<Resume> Resumes { get; set; }
}
You would set the DataContext of the LongListSelector to be an instance of the ResumeContainer. This would preferably be a property of your ViewModel. You would need to change your xaml to be
<phone:LongListSelector x:Name="ListeSolde" ItemsSource="{Binding Resumes}">
Your code-behind then changes to
List<resume> soldes = new List<resume>();
Donnees MainData = new Donnees();
soldes = MainData.RefreshResume(soldes);
ListeSolde.DataContext = new ResumeContainer { Resumes = soldes };
I'm trying to code an rssreader and would be pleased for some architecture hints.
My reader main window hosts two wpf pages which are loaded into frames, it's a "bottombar" where user can select different rss providers. In the main frame (or page) is my listview.
Because of an loading animation and UI Freeze I've an extra class with a backgroundworker which fills an observable collection with RSS Data, when I'm debugging, it fills my collection correctly.
In main page i'm setting the datacontext to this observable collection but listview doesn't show anything, here I'm stuck.
That's what I have:
MainPage XAML:
> <ListBox ItemsSource="{Binding}" DisplayMemberPath="RssTitle"
> IsSynchronizedWithCurrentItem="True"
> SelectionChanged="itemsList_SelectionChanged"
> ItemContainerStyle="{DynamicResource listboxitem_style}" Height="396"
> HorizontalAlignment="Left" Margin="126,12,0,0" Name="ListBox1"
> VerticalAlignment="Top" Width="710"></ListBox>
ListBox1.DataContext = GetRssItems.observable_list;
Bottompage to get another rss feed:
GetRssItems getitems = new GetRssItems();
GetRssItems.observable_collection = null;
getitems.start_bg_worker("url");
GetRssItems.cs
public class GetRssItems
{
public static ObservableCollection<RSSItem> observable_collection { get; set; }
public static string tmp_url;
public BackgroundWorker worker = new BackgroundWorker();
public void start_bg_worker(string url)
{
if (!worker.IsBusy)
{
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync(url);
}
}
}
In BackgroundWorkers DoWork I'm receiving rss items with linq and add it to my observable collection:
observable_collection.Add(new RSSItem(item.tmp_Title, item.tmp_Link, item.tmp_Description, item.tmp_pubDate, item.tmp_ImageUrl));
Seperate class RSSItem.cs
public class RSSItem
{
public string RssTitle { get; set; }
public string RssLink { get; set; }
public string RssDescription { get; set; }
public string RsspubDate { get; set; }
public string RssImageUrl { get; set; }
public RSSItem(string rsstitle, string rsslink, string rssdescription, string rsspubdate, string rssimageurl)
{
RssTitle = rsstitle;
RssLink = rsslink;
RssDescription = rssdescription;
RsspubDate = rsspubdate;
RssImageUrl = rssimageurl;
}
}
Thanks for your time and hints.
Best Regards
You need to read up a bit MVVM to get the most benefit from WPF. Your line setting the listbox's datacontext is rather confusing.
What you should have is your main window's (xaml) data context set to a view model class that contains your observable collection. The list box's ItemsSource is set to that property name.
For example:
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<RSSItem> RSSItems
{
get;
set;
}
// Other stuff applicable to the main window.
}
When the view is constructed, pass an instance of the MainViewModel to it's DataContext. Then the Xaml for the ListBox would be:
<ListBox ItemsSource={Binding Path=RSSItems} ... />
If you want to be able to set/change the RSSItems collection instance (I.e. public setter) then you should set it up it's setter with the NotifyPropertyChanged event, however if you just add/remove items then this shouldn't be necessary. (I.e. loading populating the items in the constructor.)
use the following:
the data context should be the Object getitems
<ListBox ItemsSource="{Binding observable_collection}" Height="167" Margin="0" Name="listBox1" Width="330" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Top">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding RssTitle}" FontWeight="Bold" FontSize="16" />
<TextBlock Text="{Binding RssLink}" FontSize="16"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
PS:
your naming is HORRBILE
Is that even possible? I have two ObservableCollections. I want to bind and populate a listbox with one of them. For example let's say that we have 2 buttons - one for Twitter and one for Facebook. Clicking on a Facebook button it will populate listbox with friend's names from facebook observable collection and it will bind it. Clicking on Twitter it will populate listbox with Twitter followers and populate listbox and bind it.
How to choose which collection will be populated in listbox?
I would just use one observable collection and fill based on the users choice. You could also fill it with the names from both sources and have a filter to filter out one or the other (apparently you need a wrapper object where you can indicate whether the name is a facebook friend or twitter follower).
Edit: Here is some quick code example of how you can do it:
public interface ISocialContact
{
string Name { get; }
}
public class FacebookContact : ISocialContact
{
public string Name { get; set; }
public string FacebookPage { get; set; }
}
public class TwitterContact : ISocialContact
{
public string Name { get; set; }
public string TwitterAccount { get; set; }
}
Then in your data context:
public ObservableCollection<ISocialContact> Contacts { get; set; }
...
Contacts = new ObservableCollection<ISocialContact> {
new FacebookContact { Name = "Face", FacebookPage = "book" },
new TwitterContact { Name = "Twit", TwitterAccount = "ter" }
};
And in your xaml:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:FacebookContact}">
<TextBlock Text="{Binding FacebookPage}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:TwitterContact}">
<TextBlock Text="{Binding TwitterAccount}"/>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Contacts}" Width="100" Height="100"/>
</Grid>
This will apply the appropriate template to each object in your collection. So you can have collection with just facebook contacts or just twitter contacts or mixed.
Also note: You do not need the common interface. It will also work if you just make your ObservableCollection of type object. But given that they are being displayed by the same app in the same list box indicates that you can find some kind of common base and either can create a comon interface or base class.
In your ViewModel, create a property that exposes one or the other ObservableCollection, and swap it out when the button is clicked:
private ObservableCollection<string> _twitterFriendList;
private ObservableCollection<string> _facebookFriendList;
private ObservableCollection<string> _selectedFriendList;
public ObservableCollection<string> SelectedFriendList
{
get { return _selectedFriendList; }
set
{
if (value != _selectedFriendList)
{
_selectedFriendList = value;
RaisePropertyChanged("SelectedFriendList");
}
}
}
void TwitterButton_Click(object sender, RoutedEventArgs e)
{
SelectedFriendList = _twitterFriendList;
}
void FacebookButton_Click(object sender, RoutedEventArgs e)
{
SelectedFriendList = _facebookFriendList;
}
Then in your XAML you can just bind to the property:
<ListBox ItemsSource="{Binding SelectedFriendList}"/>
A non-elegant way of accomplishing this is to put 2 listboxes in the same location and bind 1 to the twitter collection and the other to the facebook collection. Bind their visibility to a property that changes based upon the button clicks. Personally, I'd have 2 radio buttons and display the listbox based upon which one is selected.
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=TwitterCollection}" SelectedItem="{Binding Path=SelectedTwitterItem}" Visibility="{Binding Path=IsTwitterSelected, Converter={StaticResource visibilityConverter}}" />
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=FacebookCollection}" SelectedItem="{Binding Path=SelectedFacebookItem}" Visibility="{Binding Path=IsFacebookSelected, Converter={StaticResource visibilityConverter}}" />
<RadioButton Grid.Row="1" Grid.Column="0" GroupName="rdoOptions" Content="{Binding Path=TwitterLabel}" IsChecked="{Binding Path=IsTwitterSelected}" />
<RadioButton Grid.Row="1" Grid.Column="1" GroupName="rdoOptions" Content="{Binding Path=FacebookLabel}" IsChecked="{Binding Path=IsFacebookSelected}" />