I using LongListSelector for showing Groups with Contact (one Contact can belong to multiple Groups)
class Group
{
public string Name { get; set; }
}
class Contact
{
public string Name { get; set; }
public List<Group> Groups { get; set; }
}
I use code below to build ItemsSource for LongListSelector
public List<KeyedList<Group, Contact>> GroupedContacts
{
get
{
List<Group> groups = ...;
List<Contact> contacts = ...;
List<KeyedList<Group, Contact>> result
= new List<KeyedList<Group, Contact>>();
foreach (Group gr0up in groups)
{
var temp = from c in contacts where c.Groups.Contains(gr0up) select c;
List<Contact> groupedContacts = new List<Contact>(temp);
result.Add(new KeyedList<Group, Contact>(gr0up, groupedContacts));
}
return result;
}
}
As you can see from the code above, single contact object can be used in several groups.
I handle SelectionChanged event and I can get a selected Contact but I can't get information about the selected Group.
Is there any 'standard' possibility to know, which Group has been selected?
Update
<Grid>
<Grid.Resources>
<DataTemplate x:Key="GroupHeader">
<Grid Margin="3,3" Width="480" Height="90" HorizontalAlignment="Stretch"
Hold="GroupDelete" Tag="{Binding Key.Name}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Key.Name}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ItemTemplate">
<StackPanel Height="128" Width="128" Orientation="Vertical">
<Grid>
<Image Width="102" Height="102" VerticalAlignment="Top" HorizontalAlignment="Left" Stretch="UniformToFill">
<Image.Source>
<BitmapImage UriSource="/Assets/contact_template.png" CreateOptions="BackgroundCreation"/>
</Image.Source>
</Image>
<Image Width="36" Height="36" VerticalAlignment="Top" HorizontalAlignment="Right"
Margin="10,5,35,0"
Source="/Assets/delete_contact.png"/>
</Grid>
<TextBlock Text="{Binding Name}" Foreground="Black" VerticalAlignment="Top"/>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<phone:LongListSelector Name="ContactsList"
ItemsSource="{Binding GroupedContacts}"
ItemTemplate="{StaticResource ItemTemplate}"
GroupHeaderTemplate="{StaticResource GroupHeader}"
Style="{StaticResource ContactsLongListSelectorStyle}"
SelectionChanged="ContactsList_SelectionChanged"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"/>
</Grid>
KeyedList implementation:
public class KeyedList<TKey, TItem> : List<TItem>
{
public TKey Key { protected set; get; }
public KeyedList(TKey key, IEnumerable<TItem> items)
: base(items)
{
Key = key;
}
public KeyedList(IGrouping<TKey, TItem> grouping)
: base(grouping)
{
Key = grouping.Key;
}
}
In ContactsList_SelectionChanged(sender,e): e.AddedItems[0] should give you a group/value pair.
UPDATE: To receive a key/value pair in SelectionChanged(), each list item of LongListSelector needs to be a key/value pair.
Here's a walkthrough for LongListSelector (It's specialized in that the groups are strings and calculated from the value, but it can easily be made more generic):
http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj244365%28v=vs.105%29.aspx
So the example's AddressBook class would for you be something like
public class LongListGroupEntry
{
public Group Key;
public Contact Value;
}
Related
I am using a collectionview in a xamarin.Forms app. I want to identify the Group Key of a SelectedItem.
The items in each group are not unique. Item can appear in multiple groups. Perhaps I could use SelectionChangedCommand and specify the CommandParameter as the label.text in the GroupHeaderTemplate?
It is possible.
Based on Xamarin.Forms - CollectionView, we could use SelectionChanged method to get the curren selected item. Then we can loop which group contain this item, therefore the property of Group also will get.
We will modifl VerticalListEmptyGroupsPage.Xaml code as follows:
<StackLayout Margin="20">
<CollectionView ItemsSource="{Binding Animals}"
SelectionChanged="CollectionView_SelectionChanged"
SelectionMode="Single"
IsGrouped="true">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.GroupHeaderTemplate>
<DataTemplate>
<Label Text="{Binding Name}"
BackgroundColor="LightGray"
FontSize="Large"
FontAttributes="Bold" />
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
<CollectionView.GroupFooterTemplate>
<DataTemplate>
<Label Text="{Binding Count, StringFormat='Total animals: {0:D}'}"
Margin="0,0,0,10" />
</DataTemplate>
</CollectionView.GroupFooterTemplate>
</CollectionView>
</StackLayout>
And VerticalListEmptyGroupsPage.xaml.cs:
public partial class VerticalListEmptyGroupsPage : ContentPage
{
static GroupedAnimalsViewModel groupedAnimals;
public VerticalListEmptyGroupsPage()
{
InitializeComponent();
groupedAnimals= new GroupedAnimalsViewModel(true);
BindingContext = groupedAnimals;
}
private void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Animal selectedAnimal = e.CurrentSelection[0] as Animal;
foreach (var animals in groupedAnimals.Animals)
{
foreach(var animal in animals)
{
if(animal == selectedAnimal)
{
Console.WriteLine(animals.Name);
DisplayAlert("Group Name", animals.Name, "OK");
}
}
}
}
}
The effect:
===============================Update====================================
If there are multi groups contain the same Item, I think the best way is to design the model of item with containing the Group key.
For example, the Animal model could be designed as follows:
public class Animal
{
public string GroupKey { set; get; }
public string Name { get; set; }
public string Location { get; set; }
public string Details { get; set; }
public string ImageUrl { get; set; }
public override string ToString()
{
return Name;
}
}
This way is similar with the foreign key of database.
I am new to C# and WPF and still learning the ropes. I am currently trying use a ListBox to display some predefined items in a list. I am using an ObservableCollection to hold those items and I am binding that collection to that ListBox. I am also allowing the user to add new items to the list or update selected ones in addition to deleting them. For each item in that list I want to display a DELETE button beside it. However each button should only be visible for the items that have been added by the user and not any of the predefined items.
I am currently able to display the DELETE button for each item in the list. Therefore my question is, is it possible to set the the property of the DELETE button for each item in the list to be visible only for the items that were newly added to it and have no DELETE buttons showing for the predefined(default) items? If so, how would I go about doing that? (That is what I am struggling to figure out.)
Should I post my code?
Thanks
Here is the viewmode which has the list and the controls to add new items to the list.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox x:Name="DrinksListBox" HorizontalAlignment="Center" Height="325" Width="275" Margin="0,0,0,0" VerticalAlignment="Center" Grid.Column="0">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Type}" Width="80" Margin="0,0,10,0" Grid.Column="0"/>
<TextBlock Text="{Binding Name}" Width="80" Margin="0,0,10,0" Grid.Column="1" HorizontalAlignment="Left"/>
<Button x:Name="DrinkDeleteButton" Content="Delete" Click="CmdDeleteDrink_Clicked" HorizontalAlignment="Right" Grid.Column="2"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="DrinkNameTextBox" Grid.Column="1" HorizontalAlignment="Left" Height="45" Margin="0,0,0,100" TextWrapping="Wrap" Text="Enter Drink Name" VerticalAlignment="Center" Width="240" FontSize="20" VerticalContentAlignment="Center"/>
<ComboBox x:Name="DrinkTypeComboBox" Grid.Column="1" HorizontalAlignment="Left" Margin="0,47,0,0" VerticalAlignment="Top" Width="240" Height="45" ItemsSource="{Binding Drinks, Mode=OneWay}" DisplayMemberPath="Type" FontSize="20"/>
<Button x:Name="AddDrinkButton" Content="Add Drink" Grid.Column="1" HorizontalAlignment="Right" Margin="0,0,10,100" VerticalAlignment="Center" Width="100" Height="45" Click="CmdAddDrink_Clicked"/>
</Grid>
Here is my code-behind. I have a inner class for the drink property and the main class that sets up the list to be used.
public partial class MainWindow : Window
{
public ObservableCollection<Drinks> Drinks { get; private set; }
public MainWindow()
{
InitializeComponent();
Drinks = new ObservableCollection<Drinks>();
Drinks.Add(new Drinks("Soda", "Pepsi"));
Drinks.Add(new Drinks("Tea", "Lemon"));
Drinks.Add(new Drinks("Caffinated", "Coffee"));
Drinks.Add(new Drinks("Other", "Water"));
DrinksListBox.ItemsSource = Drinks;
DrinkTypeComboBox.ItemsSource = Drinks;
}
private void CmdDeleteDrink_Clicked(object sender, RoutedEventArgs e)
{
Button cmd = (Button)sender;
if (cmd.DataContext is Drinks deleteDrink)
{
Drinks.Remove(deleteDrink);
}
}
private void CmdAddDrink_Clicked(object sender, RoutedEventArgs e)
{
string typeSelection = ((Drinks)DrinkTypeComboBox.SelectedItem).Type;
Drinks.Add(new Drinks(typeSelection, DrinkNameTextBox.Text));
}
}
Drink class has the type of drink and a name for it.
public class Drinks
{
private string type;
private string name;
public Drinks(string type, string name)
{
this.type = type;
this.name = name;
}
public string Type
{
get { return type; }
set
{
if (type != value)
{
type = value;
}
}
}
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
}
}
}
}
Let's say you have your item:
public class Drinks
{
//your properties, simplified for clarity
public string Name {get;set;}
public string Type {get;set;}
//hey, a new one!
public bool IsUserDefined {get;set;}
}
Then, when the user adds one:
private void CmdAddDrink_Clicked(object sender, RoutedEventArgs e)
{
string typeSelection = ((Drinks)DrinkTypeComboBox.SelectedItem).Type;
Drinks.Add(new Drinks(typeSelection, DrinkNameTextBox.Text)
{
IsUserDefined = true
});
}
disclaimer: from the top of my head; normally that means syntax errors; removed some parts for clarity.
<!-- In your resources section of the XAML -->
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<ListBox x:Name="DrinksListBox" ItemSource="{Binding Drinks}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Type}"/>
<TextBlock Text="{Binding Name}"/>
<Button x:Name="DrinkDeleteButton"
Visibility="{Binding Path=IsUserDefined,
Converter={StaticResource BoolToVis}}"/>
<!-- note: left out some attributes for clarity -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
That should do the trick.
Btw, you seem to be mixing some typical MVVM style coding and code-behind coding. It's worth to say that you might benefit by using a ViewModel in your code.
i have a multicolumn ListBox as you can see below.
<ListBox x:Name="lstQuestions" HorizontalAlignment="Left" Height="544" VerticalAlignment="Top" Width="175" Margin="0,0,0,-0.5" SelectionChanged="lstQuestions_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding qID}"/>
<Image Source="{Binding imgPath}" Width="140" Height="50" Stretch="UniformToFill" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I want to show the index number (like 1, 2, 3 ...) instead of <TextBlock Text="{Binding qID}"/> in first column, how can I do that?
Edit: Binding code behind
public class QList
{
public string qID { get; set; }
public string imgPath { get; set; }
public int ansCount { get; set; }
public string rightAns { get; set; }
}
and
public static List<QList> questions = new List<QList>();
last one
lstQuestions.ItemsSource = Test.questions;
The first two are in a class called Test
My ListBox control is working fine, except that the data to be bound is not displaying.
My XAML:
<ListBox x:Name="listFileNames" SelectionMode="Single" Margin="10">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Margin="5" Source="{Binding Path=Image}" Stretch="Fill" Width="50" Height="50"></Image>
<StackPanel Grid.Column="1" Margin="5">
<TextBlock Text="{Binding Path=FileName}" FontWeight="Bold"></TextBlock>
<TextBlock Text="{Binding Path=State}"></TextBlock>
<TextBlock Text="This text shows..."></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
My code:
public struct StyleDocumentFile
{
public string Image;
public string FileName;
public string State;
}
// ......
StyleDocumentFile sdf = new StyleDocumentFile()
{
Image = "/Images/Loading.png",
FileName = "abc",
State = "Extracting Data...",
};
this.listFileNames.Items.Add(sdf);
Change fields to Property. After this, all works fine.
public struct StyleDocumentFile
{
public string Image { get; set; }
public string FileName { get; set; }
public string State { get; set; }
}
You should set ItemsSource in ListBox definition like ItemsSource="{Binding Model.Items}". In addition you must call RaisePropertyChanged in setter of model properties.
So, I am on my way learning MVVM Pattern for Windows Phone, and stuck how to bind the View to my ViewModel. App that I build now is getting current and next 5 days weather and display it to one of my panorama item on MainPage.xaml using UserControl.
I cannot just simply set the Forecasts.ItemsSource = forecast; in my WeatherViewModel, it says that Forecasts (Listbox element name in WeatherView) not exist in the current context.
Can anybody teach me how to bind it? and anybody have a good source/example sample to mvvm pattern in windows-phone? Thanks before.
EDIT:
WeatherModel.cs
namespace JendelaBogor.Models
{
public class WeatherModel
{
public string Date { get; set; }
public string ObservationTime { get; set; }
public string WeatherIconURL { get; set; }
public string Temperature { get; set; }
public string TempMaxC { get; set; }
public string TempMinC { get; set; }
public string Humidity { get; set; }
public string WindSpeedKmph { get; set; }
}
}
WeatherViewModel.cs
namespace JendelaBogor.ViewModels
{
public class WeatherViewModel : ViewModelBase
{
private string weatherURL = "http://free.worldweatheronline.com/feed/weather.ashx?q=";
private const string City = "Bogor,Indonesia";
private const string APIKey = "APIKEY";
private IList<WeatherModel> _forecasts;
public IList<WeatherModel> Forecasts
{
get
{
if (_forecasts == null)
{
_forecasts = new List<WeatherModel>();
}
return _forecasts;
}
private set
{
_forecasts = value;
if (value != _forecasts)
{
_forecasts = value;
this.NotifyPropertyChanged("Forecasts");
}
}
}
public WeatherViewModel()
{
WebClient downloader = new WebClient();
Uri uri = new Uri(weatherURL + City + "&num_of_days=5&extra=localObsTime&format=xml&key=" + APIKey, UriKind.Absolute);
downloader.DownloadStringCompleted += new DownloadStringCompletedEventHandler(ForecastDownloaded);
downloader.DownloadStringAsync(uri);
}
private void ForecastDownloaded(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Result == null || e.Error != null)
{
MessageBox.Show("Cannot load Weather Forecast!");
}
else
{
XDocument document = XDocument.Parse(e.Result);
var current = from query in document.Descendants("current_condition")
select new WeatherModel
{
ObservationTime = DateTime.Parse((string)query.Element("localObsDateTime")).ToString("HH:mm tt"),
Temperature = (string)query.Element("temp_C"),
WeatherIconURL = (string)query.Element("weatherIconUrl"),
Humidity = (string)query.Element("humidity"),
WindSpeedKmph = (string)query.Element("windspeedKmph")
};
this.Forecasts = (from query in document.Descendants("weather")
select new WeatherModel
{
Date = DateTime.Parse((string)query.Element("date")).ToString("dddd"),
TempMaxC = (string)query.Element("tempMaxC"),
TempMinC = (string)query.Element("tempMinC"),
WeatherIconURL = (string)query.Element("weatherIconUrl")
}).ToList();
}
}
}
}
WeatherView.xaml
<UserControl x:Class="JendelaBogor.Views.WeatherView"
xmlns:vm="clr-namespace:JendelaBogor.ViewModels">
<UserControl.DataContext>
<vm:WeatherViewModel />
</UserControl.DataContext>
<Grid Margin="0,-10,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="Current" Grid.Row="0" Height="150" VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" delay:LowProfileImageLoader.UriSource="{Binding WeatherIconURL}" Width="120" Height="120" VerticalAlignment="Top"/>
<StackPanel Grid.Column="1" Height="200" VerticalAlignment="Top">
<TextBlock Text="{Binding Temperature}" FontSize="22"/>
<TextBlock Text="{Binding ObservationTime}" FontSize="22"/>
<TextBlock Text="{Binding Humidity}" FontSize="22"/>
<TextBlock Text="{Binding Windspeed}" FontSize="22"/>
</StackPanel>
</Grid>
<Grid Grid.Row="1" Height="300" VerticalAlignment="Bottom" Margin="10,0,0,0">
<StackPanel VerticalAlignment="Top">
<StackPanel Height="40" Orientation="Horizontal" Margin="0,0,0,0">
<TextBlock Text="Date" FontSize="22" Width="170"/>
<TextBlock Text="FC" FontSize="22" Width="60"/>
<TextBlock Text="Max" TextAlignment="Right" FontSize="22" Width="90"/>
<TextBlock Text="Min" TextAlignment="Right" FontSize="22" Width="90"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ListBox ItemsSource="{Binding Forecasts}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Height="40" Orientation="Horizontal" Margin="0,10,0,0">
<TextBlock Text="{Binding Date}" FontSize="22" TextAlignment="Left" Width="170" />
<Image delay:LowProfileImageLoader.UriSource="{Binding WeatherIconURL}" Width="40" Height="40" />
<TextBlock Text="{Binding TempMaxC, StringFormat='\{0\} °C'}" TextAlignment="Right" FontSize="22" Width="90" />
<TextBlock Text="{Binding TempMinC, StringFormat='\{0\} °C'}" TextAlignment="Right" FontSize="22" Width="90" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</StackPanel>
</Grid>
</Grid>
</UserControl>
MainPage.xaml
<controls:PanoramaItem x:Name="Weather" Header="weather">
<views:WeatherView />
</controls:PanoramaItem>
You need to tell the view what viewmodel you are using. By adding
<UserControl
xmlns:vm="clr-namespace:JendelaBogor.ViewModels">
<UserControl.DataContext>
<vm:WeatherViewModel />
</UserControl.DataContext>
</UserControl>
all {Binding}'s are mapped to the class WeatherViewModel. By using the ItemsSource property on the listbox as Reed suggests you can then bind all items from a list that you expose through a property.
If the list is ever changed while running the application, consider using an ObservableCollection and clearing it and adding all new items when new data is received. If you do, your GUI will simply update with it.
The ViewModel doesn't know about the view.
You need to make a Forecasts property on the ViewModel, and bind the ItemsSource to it from your View. In your view, change the ListBox to:
<!-- No need for a name - just add the binding -->
<ListBox ItemsSource="{Binding Forecasts}">
Then, in your ViewModel, add:
// Add a backing field
private IList<WeatherModel> forecasts;
// Add a property implementing INPC
public IList<WeatherModel> Forecasts
{
get { return forecasts; }
private set
{
forecasts = value;
this.RaisePropertyChanged("Forecasts");
}
}
You can then set this in your method:
this.Forecasts = (from query in document.Descendants("weather")
select new WeatherModel
{
Date = DateTime.Parse((string)query.Element("date")).ToString("dddd"),
TempMaxC = (string)query.Element("tempMaxC"),
TempMinC = (string)query.Element("tempMinC"),
WeatherIconURL = (string)query.Element("weatherIconUrl")
})
.ToList(); // Turn this into a List<T>