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 };
Related
I'm new to WPF and MVVM ... i created a class WorkstationItem
public class WorkstationItem
{
public WorkstationItem() { }
public string Name { get; set; }
public string OS { get; set; }
public List<UpdateItem> Updates { get; set; }
}
UpdateItem is another class:
public class UpdateItem
{
public UpdateItem() { }
public string Title { get; set; }
public string KB { get; set; }
}
I create some dummy data:
private List<WorkstationItem> workstations = new List<WorkstationItem>();
workstations.Add(new WorkstationItem
{
Name = "PC01",
OS = "Windows Server 2019",
Updates = new List<UpdateItem>{
new UpdateItem { Title = "Test", KB = "KB123123" },
new UpdateItem { Title = "Test2", KB = "KB123123" }
}
});
workstations.Add(new WorkstationItem
{
Name = "PC02",
OS = "Windows Server 2016",
Updates = new List<UpdateItem>{
new UpdateItem { Title = "Test5", KB = "KB123123" },
new UpdateItem { Title = "Test3", KB = "KB123123" }
}
});
Now i show the workstations in a listbox:
<ListBox x:Name="lbPCs" ItemsSource="{Binding WorkstationItemList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="{Binding Name}" FontSize="12" FontWeight="Bold" />
<TextBlock Text="{Binding OS}" FontSize="9" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel:
public ObservableCollection<WorkstationItem> WorkstationItemList { get; set; }
WorkstationManager workstationmanager = WorkstationManager.GetInstance();
WorkstationItemList = new ObservableCollection<WorkstationItem>();
foreach (var k in workstationmanager.GetUpdatelist())
{
WorkstationItemList.Add(k);
}
This is working fine ... but how can i show the List<UpdateItem> Updates in another list in relation to the selected workstation?
So i select a workstation in list1 and want to show the related updates in list2?
Thanks in advance!
Basically add another ListBox or ItemsControl that refers to SelectedItem of the existing ListBox and bind to the SelectedItem's Updates collection; roughly like (untested):
<ListBox ItemsSource="{Binding ElementName=lbPCs, Path=SelectedItem.Updates}">
<ListBox .ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!-- TODO improve alignment+layout -->
<TextBlock Text="{Binding KB}" />
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
<ListBox .ItemTemplate>
</ListBox>
In case you want to do something more complex with the selected item but to display its Updates, it might be more appropriate to bind the SelectedItem of lbPCs to some new ViewModel property and to bind the new ListBox' itemsource to that VM property's Updates collection.
I have a problem, I have a list View lets call it ListView1, that is populated dynamically from local storage after a sync from Azure.
Now I am having a problem selecting and getting information from the item click.
I need to get four different types of information and pass it to a flyout when there is an item click on the listview.
//This is the way the List is Created
string DB_PATH = Path.Combine(ApplicationData.Current.LocalFolder.Path, "my.db");
public async void CreateList ()
{
await AzureWebService.Instance.InitLocalStoreAsync(DB_PATH);
var t = await AzureWebService.Instance.GetListItems(); //GET THE OG LIST
var list = new ObservableCollection<winMerchant>(); //create a new list of Names, (with the BitmapImage Field )
foreach (var f in t)
{
var m = new winDirectory(); //give the customized merchant object, the same VALUES as the ACTUAL mercahnt object
m.MyName1 = f.MyName1;
m.MyName2 = f.MyName2;
m.MyNumber = f.MyNumber; //int
m.MyPic = ImageHelper.Base64StringToBitmap(f.MyPic); //CUSTOM PART ==> Take the newly defined BitmapImage (wMyPic) and convert the Pic string into it
list.Add(m); //add the new People into a list
}
MyList.ItemsSource = list;
}
And this is the xaml
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Stretch">
<Image Source="{Binding wPic}" Width="75" Height="75" />
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<TextBlock Text="{Binding wMyName1}" />
<TextBlock Text="{Binding wMyName2}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
Helper Class
class winMerchant: Contacts
{
public BitmapImage wPic { get; set; }
public int wMyNumber { get; set; }
public string wMyName1 { get; set; }
public string wMyName2 { get; set; }
}
The Number should be passed to a flyout on click...
Any help will be much appreciated!
You can just get the selected item's number from item click event of ListView.
private void MainListView_ItemClick(object sender, ItemClickEventArgs e)
{
var item = e.ClickedItem as winMerchant;
int number = item.wMyNumber;
}
Also make IsItemClickEnabled="True" of your Listview.
Try to add
AutomationProperties.Name="{Binding wMyNumber}" to the <Image>
With this you can pass the number of the clicked object, in the c# (inside the click event) you can just create a List<> or an Array with data about your objects (so you can use this collection of object to display it in the flyout)
Currently I am trying to implement an observable collection which is bound to a data template (WPF-MVVM). During initialization it loads the default value to observable collection. Idea is:
User provides some value on the textbox,
presses ENTER key
increments a counter and updates the count value on textblock which is located near the text box.
The purpose is to track how times the text value has been changed by the user.
Right now it is working with 'IndexOf', 'RemoveAt' and 'Insert'. Is there a way to do without 'RemoveAt' and 'Insert'.
I feel something wrong on my code? Can anybody help it.
InputDataTemplate.xaml:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Name}" />
<Label Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Count}" />
<TextBox x:Name="IpDataTb" Grid.Column="1" Width="60" HorizontalAlignment="Center" VerticalAlignment="Center" DataContext="{Binding}" Text="{Binding Path=Data, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<ei:CallMethodAction TargetObject="{Binding }" MethodName="IpDataTrig" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</Grid>
TestView.xaml:
<UserControl.Resources>
<DataTemplate x:Key="InputDataTemplate" >
<local:InputDataTemplate DataContext="{Binding}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
<Border BorderBrush="#FF0254B4" BorderThickness="1" >
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
<ItemsControl ItemsSource="{Binding InputDatas}"
ItemTemplate="{DynamicResource InputDataTemplate}" />
</ScrollViewer>
</Border>
</Grid>
DataService.cs:
using MyObsrCollTest.ViewModels;
namespace MyObsrCollTest.Services
{
public class InputDataService : BindableBase
{
public string Name { get; set; }
public string Count { get; set; }
public string Data { get; set; }
public void IpDataTrig(object sender,KeyEventArgs e)
{
var IpDataTb = new TextBox();
IpDataTb = (TextBox)sender;
if ((e.Key == Key.Enter) &&(!string.IsNullOrWhiteSpace(IpDataTb.Text)))
{
this.Data = IpDataTb.Text;
ObsrCollTestVm.TestMe(this.Name, this.Data);
}
}
}
}
ObsrCollTestVm.cs:
private ObservableCollection<InputDataService> _InputDatas;
static int _count = 0;
public ObsrCollTestVm(void)
{
for (int i = 0; i < 5; i++)
{
var l_InputDatas = new InputDataService();
l_InputDatas.Name = i.ToString();
l_InputDatas.Count = "0";
l_InputDatas.Data = "?";
_InputDatas.Add(l_InputDatas);
}
}
Basic initialization routine:
public ObservableCollection<InputDataService> InputDatas
{
get
{
if (_InputDatas == null)
{
_InputDatas = new ObservableCollection<InputDataService>();
}
return _InputDatas;
}
}
New Observable collection:
public static void TestMe(string name, string data)
{
var found = _InputDatas.FirstOrDefault(element = > element.Name == name);
if (found != null)
{
int i = _InputDatas.IndexOf(found);
found.Count = _count++;
_InputDatas.RemoveAt(i);
_InputDatas.Insert(i, found);
}
}
Increment the count value:
If I understand the question correctly, it can be summarized as:
"I would like to be able to change the Count property of my InputDataService class objects and have that change reflected in that item's Label, without having to modify the ObservableCollection<InputDataService> itself."
Is that correct?
If so, then the solution is for your InputDataService class to correctly provide notifications of property changes. Normally, this would mean either inheriting DependencyObject and implementing your properties as dependency properties, or just implementing the INotifyPropertyChanged interface.
But in your example, you seem to be inheriting a class named BindableBase already. If that class is in fact the Microsoft.Practices.Prism.Mvvm.BindableBase class, then it already implements INotifyPropertyChanged and all you need to do is take advantage of that.
For example:
public class InputDataService : BindableBase
{
private int _count;
public string Name { get; set; }
public int Count
{
get { return _count; }
set { SetProperty(ref _count, value); }
}
public string Data { get; set; }
public void IpDataTrig(object sender,KeyEventArgs e)
{
var IpDataTb = new TextBox();
IpDataTb = (TextBox)sender;
if ((e.Key == Key.Enter) &&(!string.IsNullOrWhiteSpace(IpDataTb.Text)))
{
this.Data = IpDataTb.Text;
ObsrCollTestVm.TestMe(this.Name, this.Data);
}
}
}
Notes:
In the above, I only fixed the issue for the Count property. You can apply similar changes to the other properties to get them to update correctly.
In your TestMe() method, you seem to be using the Count property as an int, but it was declared in your code example as a string. Lacking a better way to reconcile that discrepancy in your code example, I've just changed the property declaration in the example above to use int instead of string.
This example assumes you are using .NET 4.5, in which the [CallerMemberName] attribute is supported. If you're using an earlier version of .NET, then you will need to add the property name to the SetProperty() call. E.g.: SetProperty(ref _count, value, "Count");
With these changes, you should be able to write TestMe() like this:
public static void TestMe(string name, string data)
{
var found = _InputDatas.FirstOrDefault(element = > element.Name == name);
if (found != null)
{
found.Count = _count++;
}
}
I'm using WPF, I have a window with a listview which is binded to an ObservableCollection.
So It's looks like that:
public ObservableCollection<Task> TaskList { get; set; }
Task being a model
public class Task
{
public int Id { get; set; }
public string Name { get; set; }
....
}
The XAML of the ListView.ItemTemplate:
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
I cannot use the Id because items in the collection can be deleted or sorted, I need the index in the collection (with update if the collection get changed).
I would like to find a way to add an "index column" to the listview, so the index of the Task in the ObservableCollection would be before the name in every row (see below).
| 0 Task
| 1 TaskTest
| 2 OtherTask
| 3 LastTask
Thanks for your help!
As far as I know it's not that easy to add index :(
If I was You, I'll add a property to item, that is shown in Your listview. That property would be filled with ViewModel and incremented. But if You sort or delete it should be rewritten.
You can also try making a property inside Your ViewModel like:
private int counter;
public int Index
{
get
{
counter++;
return counter;
}
}
and Bind it to each element using RelativeSource FindAncestor. but again - it should be refreshed on collectionChange
try this,crate a view model
public class TaskViewModel : Task
{
private int _Index;
public int Index
{
get { return _Index; }
set { _Index = value; }
}
}
ItemTemplate:
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel Orientation="Horizontal">
<TextBlock Text="{Binding Index}" Width="Auto" Height="Auto" Margin="0,0,4,0"/>
<TextBlock Text="{Binding Name}" Width="Auto" Height="Auto"/>
</WrapPanel>
</ListView.ItemTemplate>
code behind:
TaskViewModel vm = new TaskViewModel();
vm.Id = 0;
vm.Index = 1;
vm.Name = "sad";
TaskList.Add(vm);
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