Update list view on expand - xamarin

I am coding a Xamarin.Forms project and I have a list view but whenever I show hidden content, for example, make an entry visible it the ViewCell overlaps the one beneath it.
Is there a way I could .Update() the listview or something to refresh it and make them all fit.
I don't want the refresh to cause it to go back to the top though.
Android seems to be able to automatically update the height when I show something.
I tried using HasUnevenRows="True" but that still didn't fix it.
Code:
Message.xaml
<StackLayout>
<local:PostListView x:Name="MessageView" HasUnevenRows="True" IsPullToRefreshEnabled="True" Refreshing="MessageView_Refreshing" SeparatorVisibility="None" BackgroundColor="#54a0ff">
<local:PostListView.ItemTemplate>
<DataTemplate>
<local:PostViewCell>
<StackLayout>
<Frame CornerRadius="10" Padding="0" Margin="10, 10, 10, 5" BackgroundColor="White">
<StackLayout>
<StackLayout x:Name="MessageLayout" BackgroundColor="Transparent" Padding="10, 10, 15, 10">
...
<Label Text="{Binding PostReply}" FontSize="15" TextColor="Black" Margin="10, 0, 0, 10" IsVisible="{Binding ShowReply}"/>
<StackLayout Orientation="Vertical" IsVisible="{Binding ShowReplyField}" Spacing="0">
<Entry Text="{Binding ReplyText}" Placeholder="Reply..." HorizontalOptions="FillAndExpand" Margin="0, 0, 0, 5"/>
...
</StackLayout>
<StackLayout x:Name="MessageFooter" Orientation="Horizontal" IsVisible="{Binding ShowBanners}">
<StackLayout Orientation="Horizontal">
...
<Image x:Name="ReplyIcon" Source="reply_icon.png" HeightRequest="20" HorizontalOptions="StartAndExpand" IsVisible="{Binding ShowReplyButton}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ReplyClick}" CommandParameter="{Binding .}"/>
</Image.GestureRecognizers>
</Image>
...
</StackLayout>
...
</StackLayout>
</StackLayout>
</StackLayout>
</Frame>
</StackLayout>
</local:PostViewCell>
</DataTemplate>
</local:PostListView.ItemTemplate>
</local:PostListView>
</StackLayout>
Message.cs
using Newtonsoft.Json;
using SocialNetwork.Classes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace SocialNetwork
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MessagePage : ContentPage
{
public MessagePage()
{
InitializeComponent();
LoadPage();
}
private async void LoadPage()
{
await LoadMessages();
}
private async void RefreshPage()
{
await LoadMessages();
MessageView.EndRefresh();
}
private async Task LoadMessages()
{
//*Web Request*
MessageView.ItemsSource = FormatPosts(this, Navigation, page_result);
...
}
public IList<MessageObject> FormatPosts(Page page, INavigation navigation, string json)
{
IList<MessageObject> Posts = new List<MessageObject>() { };
var messages = JsonConvert.DeserializeObject<List<Message>>(json);
foreach (var message in messages)
{
MessageObject mo = MessageObject.CreateMessage(...);
Posts.Add(mo);
}
return Posts;
}
public async void ShowOptionActions(string id, string poster_id, object message)
{
...
}
public async void ShowReportOptions(string id, string poster_id)
{
...
}
public void SubmitReplyClick(string id, object msg)
{
...
}
public async void SendReplyAsync(string id, object msg, string reply)
{
await SendReply(id, msg, reply);
}
public void ReplyCommandClick(string id, object msg)
{
MessageObject message = (MessageObject) msg;
message.ShowReplyField = message.ShowReplyField ? false : true;
//Update Cell Bounds
}
private async Task SendReply(string id, object msg, string reply)
{
MessageObject message = (MessageObject)msg;
...
message.PostReply = reply;
//Update Cell Bounds
}
public async void LikeMessageClick(string id, object message)
{
await LikeMessage(id, message);
}
private async Task LikeMessage(string id, object msg)
{
...
}
public async void DeleteMessage(string id, object msg)
{
MessageObject message = (MessageObject)msg;
message.ShowBanners = false;
message.ShowReply = false;
...
//Update Cell Bounds
}
public async Task ReportMessage(...)
{
...
}
private void MessageView_Refreshing(object sender, EventArgs e)
{
RefreshPage();
}
}
public class MessageObject : INotifyPropertyChanged
{
private Boolean showBannersValue = true;
private string replyValue = String.Empty;
private bool showReplyValue;
private bool showReplyButtonValue;
private bool showReplyFieldValue;
private Command replyCommandValue;
private Command replySubmitValue;
private string replyTextValue;
...
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private MessageObject(...)
{
...
}
public static MessageObject CreateMessage(...)
{
return new MessageObject(...);
}
public Boolean ShowBanners
{
get
{
return this.showBannersValue;
}
set
{
if (value != this.showBannersValue)
{
this.showBannersValue = value;
NotifyPropertyChanged();
}
}
}
public Boolean ShowReplyField
{
get
{
return this.showReplyFieldValue;
}
set
{
if(value != this.showReplyFieldValue)
{
this.showReplyFieldValue = value;
NotifyPropertyChanged();
}
}
}
public string PostReply
{
get
{
return this.replyValue;
}
set
{
if (value != this.replyValue)
{
this.replyValue = value;
NotifyPropertyChanged();
}
}
}
public Boolean ShowReply
{
get
{
return this.showReplyValue;
}
set
{
if(value != this.showReplyValue)
{
this.showReplyValue = value;
NotifyPropertyChanged();
}
}
}
public Boolean ShowReplyButton
{
get
{
return this.showReplyButtonValue;
}
set
{
if (value != this.showReplyButtonValue)
{
this.showReplyButtonValue = value;
NotifyPropertyChanged();
}
}
}
public string ReplyText
{
get
{
return this.replyTextValue;
}
set
{
if(value != this.replyTextValue)
{
this.replyTextValue = value;
NotifyPropertyChanged();
}
}
}
public Command ReplyClick
{
get
{
return this.replyCommandValue;
}
set
{
if (value != this.replyCommandValue)
{
this.replyCommandValue = value;
NotifyPropertyChanged();
}
}
}
...
}
}

Save your IList<MessageObject> which gets returned from your FormatPosts method in a field IList<MessageObject> _messages = new List<MessageObject>()
And use the following snippet to update the ListView whenever you need, includes a check to see if the device runs on iOS:
if(Device.RuntimePlatform == Device.iOS)
{
MessageView.ItemsSource = null;
MessageView.ItemsSource = _messages;
}

Especially with iOS there are issues resizing rows in a ListView according to changes of cells (see here). There is a method ForceUpdateSize on Cell, which should notify the ListView that the size of the cell has changed, which should cause the ListView to resize its rows.

Oh, I faced the same thing.
I guess that you just need to add this somewhere in your listview:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.row='0'>
...
</Grid>
// This, in my case, makes my cell expand when it's true. Normal behavior
<Grid Grid.row='1' isVisible="{Binding Expand}">
...
</Grid>
</Grid>
Plus, if you want to update cells individually, I use a CustomObservableCollection:
public class CustomObservableCollection<T> : ObservableCollection<T>
{
public CustomObservableCollection() { }
public CustomObservableCollection(IEnumerable<T> items) : this()
{
foreach(var item in items)
this.Add(item);
}
public void ReportItemChange(T item)
{
NotifyCollectionChangedEventArgs args =
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Replace,
item,
item,
IndexOf(item));
OnCollectionChanged(args);
}
}
With a Custom ListView to do ItemClickCommand:
public class CustomListView : ListView
{
#pragma warning disable 618
public static BindableProperty ItemClickCommandProperty = BindableProperty.Create<CustomListView, ICommand>(x => x.ItemClickCommand, null);
#pragma warning restore 618
public CustomListView(ListViewCachingStrategy cachingStrategy = ListViewCachingStrategy.RetainElement) :
base(cachingStrategy)
{
this.ItemTapped += this.OnItemTapped;
}
public ICommand ItemClickCommand
{
get { return (ICommand)this.GetValue(ItemClickCommandProperty); }
set { this.SetValue(ItemClickCommandProperty, value); }
}
private void OnItemTapped(object sender, ItemTappedEventArgs e)
{
if(e.Item != null && this.ItemClickCommand != null && this.ItemClickCommand.CanExecute(e.Item))
{
this.ItemClickCommand.Execute(e.Item);
this.SelectedItem = null;
}
}
}
then in xaml:
...
...
<Customs:CustomListView
HasUnevenRows="true"
ItemsSource="{Binding PersonList}"
IsPullToRefreshEnabled="True"
RefreshCommand="{Binding DoRefreshCommand}"
ItemClickCommand="{Binding ItemClickCommand}">
...
...
</Customs:CustomListView>
Finally:
public Command<Person> ItemClickCommand { get; set; }
...
ItemClickCommand = new Command<Person>(SelectionExecute);
...
private void SelectionExecute(Person arg)
{
arg.Expand = !arg.Expand;
foreach(var item in PersonList)
{
if(item.Key == arg.Id)// you will change this probably
item.ReportItemChange(arg);
}
}
Hope it help a bit :)

Related

Pushing ImageButton makes the image very small [Xamarin]

Please check the GIF for the problem.
I am actually using two imagebutton here and change IsVisible, since I couldn't accomplish swapping the image by Binding on the source.
ViewModel:
public bool IsAudioPlaying
{
get => player.IsPlaying;
}
...
public void PlayOrPause()
{
if (player.IsPlaying)
player.Pause();
else
player.Play();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsAudioPlaying"));
}
XAML:
<ImageButton AbsoluteLayout.LayoutBounds=".5, 0, 100, 100" AbsoluteLayout.LayoutFlags="PositionProportional" Source="play.png" Padding="20" WidthRequest="80" HeightRequest="80"
CornerRadius="40" VerticalOptions="Center" HorizontalOptions ="Center" BackgroundColor="#cea448" Clicked="PlayOrPause" Margin="10" IsVisible="{Binding IsAudioPlaying, Converter={StaticResource InverseBoolConverter}}" />
<ImageButton AbsoluteLayout.LayoutBounds=".5, 0, 100, 100" AbsoluteLayout.LayoutFlags="PositionProportional" Source="pause.png" Padding="20" WidthRequest="80" HeightRequest="80"
CornerRadius="40" VerticalOptions="Center" HorizontalOptions ="Center" BackgroundColor="#cea448" Clicked="PlayOrPause" Margin="10" IsVisible="{Binding IsAudioPlaying}" />
I am actually using two imagebutton here and change IsVisible, since I couldn't accomplish swapping the image by Binding on the source.
Create a View Model.
public class ViewModel : INotifyPropertyChanged
{
private bool _isAudioPlaying;
public bool IsAudioPlaying
{
get
{
return _isAudioPlaying;
}
set
{
_isAudioPlaying = value;
OnPropertyChanged("IsAudioPlaying");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Use ObservableCollection to update.
ObservableCollection<ViewModel> observableCollection { get; set; }
public MainPage()
{
InitializeComponent();
observableCollection = new ObservableCollection<ViewModel>()
{
new ViewModel(){ IsAudioPlaying=true}
};
this.BindingContext = observableCollection;
}
private void PlayOrPause(object sender, EventArgs e)
{
if (observableCollection[0].IsAudioPlaying == true)
{
observableCollection[0].IsAudioPlaying = false;
imageButton.Source = "pause.png";
}
else
{
observableCollection[0].IsAudioPlaying = true;
imageButton.Source = "play.png";
}
}

How to pass data from the listview tapped event to a telerik dataform in another page

I need to pass data from a ListView to a TodoDetail page where I have a Telerik DataForm, but I don't know how to make it work. If I use normal Xamarin Forms controls it works fine, but need it to work with the Telerik DataForm control.
Here is my code:
​Todo.xaml
list item tapped handler
private async void ToDoTaskTap(object sender, ItemTappedEventArgs e)
{
var user = ToDoTask.SelectedItem as tblEmpTask;
if (user != null)
{
var mainViewModel = BindingContext as MainViewModel;
if (mainViewModel != null)
{
mainViewModel.Selected = user;
await Navigation.PushAsync(new ToDoDetail(mainViewModel)); ​
}
}
}
tblEmpTask.cs
public class tblEmpTask
{
public string strTaskName { get; set; }
}
TodoDetail.xaml
<telerikInput:RadDataForm x:Name="dataForm">
<telerikInput:RadDataForm.Source>
<local1:MainViewModel />
</telerikInput:RadDataForm.Source>
</telerikInput:RadDataForm>
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
​public tblEmpTask Selected
{
get { return _Selected; }
set
{
_Selected = value;
OnPropertChanged();
}
}
​
[DisplayOptions(Header = "Name")]
public string Name
{
get { return this.Selected.strTaskName; }
set
{
if (value != this.Selected.strTaskName)
{
this.Selected.strTaskName = value;
OnPropertChanged();
}
}
}
}
You must add a binding between the SelectedItem and the ListView
Here's an example:
ViewModel:
public List<object> ItemsSource { get; set; }
public object SelectedItem {
set { SelectedItemChanged(value); }
}
async void SelectedItemChanged(object value) {
await App.Current.MainPage.Navigation.PushAsync(new AboutPage(SelectedItem));
}
Page:
<ListView
ItemsSource="{Binding ItemsSource}"
SelectedItem="{Binding SelectedItem, Mode=OneWayToSource}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding .}"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Xamarin Forms - remove extra space / embedded ListView

I am trying to figure out how to remove the white space you see in the image below (surrounded by a red rectangle). Notice I have a ListView embedded in a parent ListView.
XAML
<ListView x:Name="___listview" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Button Image="{Binding ImageName}" Command="{Binding ShowDetailsCommand}" />
<ListView ItemsSource="{Binding Notes}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Note}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This probably isn't needed, but here is the model...
MODEL
namespace ViewCellClick
{
public class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Model : ModelBase
{
public Model()
{
_imageName = "ellipses_vertical.png";
_showDetails = true;
ShowDetailsCommand = new Command(() =>
{
ShowDetails = !_showDetails;
ImageName = (_imageName == "ellipses_vertical.png")
? "ellipses_horizontal.png"
: "ellipses_vertical.png";
});
}
bool _showDetails;
public bool ShowDetails
{
get { return _showDetails; }
set { if (_showDetails != value) { _showDetails = value; OnPropertyChanged("ShowDetails"); } }
}
string _imageName;
public string ImageName
{
get { return _imageName; }
set { if (_imageName != value) { _imageName = value; OnPropertyChanged("ImageName"); } }
}
public ICommand ShowDetailsCommand { get; set; }
List<ChildModel> _notes;
public List<ChildModel> Notes { get { return _notes; } set { _notes = value; } }
}
public class ChildModel : ModelBase
{
public ChildModel(string note) { _note = note; }
string _note;
public string Note
{
get { return _note; }
set { if (_note != value) { _note = value; OnPropertyChanged("Note"); } }
}
}
}
You can't do this with Xamarin.Forms.ListView and nesting them is not supported. Really on iOS this would be very difficult and I'm not sure you could get it working without some weird gesture behavior.

Multi select drop down list in UWP

I want multi select drop down list for my mvvm cross UWP app. So is there any predefined control? Or I need to implement custom control to achieve this.
Any help or suggestions would be appreciated.
Thank You
Surprised that such common scenario is still not supported by built-in UWP ComboBox and that I couldn't found any working solutions anywhere.
Here is my minimal solution in case anyone else is looking.
XAML:
<UserControl
x:Class="MonoTorrent.GUI.Controls.MultiSelectComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<StackPanel x:Name="rootElement" Orientation="Vertical" Margin="0">
<Button x:Name="comboBoxButton" BorderBrush="Gray" BorderThickness="2" Background="Transparent"
VerticalAlignment="Top" Click="ComboBoxButton_Click"
HorizontalAlignment="Stretch" FontSize="14" MinHeight="26" Height="26" Padding="0"
Width="{Binding ElementName=rootElement, Path=ActualWidth}">
<Grid VerticalAlignment="Stretch" Width="{Binding ElementName=rootElement, Path=ActualWidth}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="32" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="SelectedValueTextBlock"
Grid.Column="0" VerticalAlignment="Center" FontSize="15" HorizontalAlignment="Left" Padding="7,0,0,0" />
<FontIcon Grid.Column="1" FontSize="12" FontFamily="Segoe MDL2 Assets" Glyph="" HorizontalAlignment="Right"
Margin="0,5,10,5" VerticalAlignment="Center" />
</Grid>
</Button>
<Popup x:Name="comboBoxPopup" IsLightDismissEnabled="True">
<Border BorderBrush="{ThemeResource ComboBoxDropDownBorderBrush}"
BorderThickness="{ThemeResource ComboBoxDropdownBorderThickness}"
Background="{ThemeResource ComboBoxDropDownBackground}"
HorizontalAlignment="Stretch">
<ListView x:Name="listView"
SelectionMode="Multiple"
SingleSelectionFollowsFocus="False"
SelectionChanged="ListView_SelectionChanged">
</ListView>
</Border>
</Popup>
</StackPanel>
</UserControl>
C#:
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace MonoTorrent.GUI.Controls
{
public sealed partial class MultiSelectComboBox : UserControl
{
#region ItemsSource dependency property
public object ItemsSource
{
get { return GetValue(ItemsSourceProperty); }
set
{
SetValue(ItemsSourceProperty, value);
listView.ItemsSource = value;
}
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(object), typeof(MultiSelectComboBox), new PropertyMetadata(new List<object>(), OnItemsSourcePropertyChanged));
private static void OnItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox instance = d as MultiSelectComboBox;
if (instance != null && e.NewValue != null)
{
instance.listView.ItemsSource = e.NewValue;
}
}
#endregion
#region ItemTemplate dependency property
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set
{
SetValue(ItemTemplateProperty, value);
listView.ItemTemplate = value;
}
}
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(MultiSelectComboBox), new PropertyMetadata(null, OnItemTemplatePropertyChanged));
private static void OnItemTemplatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox instance = d as MultiSelectComboBox;
if (instance != null && e.NewValue as DataTemplate != null)
{
instance.listView.ItemTemplate = (DataTemplate)e.NewValue;
}
}
#endregion
#region SelectedItems dependency property
public IList<object> SelectedItems
{
get { return (IList<object>)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList<object>), typeof(MultiSelectComboBox), new PropertyMetadata(new List<object>()));
#endregion
#region PopupHeight dependency property
public double PopupHeight
{
get { return (double)GetValue(PopupHeightProperty); }
set
{
SetValue(PopupHeightProperty, value);
if (value != 0)
{
listView.Height = value;
}
}
}
public static readonly DependencyProperty PopupHeightProperty =
DependencyProperty.Register("PopupHeight", typeof(double), typeof(MultiSelectComboBox), new PropertyMetadata(0.0, OnPopupHeightPropertyChanged));
private static void OnPopupHeightPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox instance = d as MultiSelectComboBox;
if (instance != null && (double)e.NewValue != 0)
{
instance.listView.Height = (double)e.NewValue;
}
}
#endregion
#region PopupWidth dependency property
public double PopupWidth
{
get { return (double)GetValue(PopupWidthProperty); }
set
{
SetValue(PopupWidthProperty, value);
if (value != 0)
{
listView.Width = value;
}
}
}
public static readonly DependencyProperty PopupWidthProperty =
DependencyProperty.Register("PopupWidth", typeof(double), typeof(MultiSelectComboBox), new PropertyMetadata(0.0, OnPopupWidthPropertyChanged));
private static void OnPopupWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox instance = d as MultiSelectComboBox;
if (instance != null && (double)e.NewValue != 0)
{
instance.listView.Width = (double)e.NewValue;
}
}
#endregion
#region NoSelectionText dependency property
public string NoSelectionText
{
get { return (string)GetValue(NoSelectionTextProperty); }
set { SetValue(NoSelectionTextProperty, value); }
}
public static readonly DependencyProperty NoSelectionTextProperty =
DependencyProperty.Register("NoSelectionText", typeof(string), typeof(MultiSelectComboBox), new PropertyMetadata("No selection"));
#endregion
#region MultipleSelectionTextFormat dependency property
public string MultipleSelectionTextFormat
{
get { return (string)GetValue(MultipleSelectionTextFormatProperty); }
set { SetValue(MultipleSelectionTextFormatProperty, value); }
}
public static readonly DependencyProperty MultipleSelectionTextFormatProperty =
DependencyProperty.Register("MultipleSelectionTextFormat", typeof(string), typeof(MultiSelectComboBox), new PropertyMetadata("{0} selected"));
#endregion
public MultiSelectComboBox()
{
this.InitializeComponent();
this.Loaded += MultiSelectComboBox_Loaded;
}
private void MultiSelectComboBox_Loaded(object sender, RoutedEventArgs e)
{
this.UpdateSelectionText();
}
private void ComboBoxButton_Click(object sender, RoutedEventArgs e)
{
listView.SelectedItems.Clear();
foreach (var item in SelectedItems)
{
listView.SelectedItems.Add(item);
}
this.comboBoxPopup.IsOpen = true;
}
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!comboBoxPopup.IsOpen)
{
return;
}
this.SelectedItems = listView.SelectedItems.ToList();
UpdateSelectionText();
}
private void UpdateSelectionText()
{
if (this.SelectedItems == null || this.SelectedItems.Count == 0)
{
this.SelectedValueTextBlock.Text = NoSelectionText;
}
else if (this.SelectedItems.Count == 1)
{
this.SelectedValueTextBlock.Text = this.SelectedItems.First().ToString();
}
else
{
this.SelectedValueTextBlock.Text = String.Format(MultipleSelectionTextFormat, this.SelectedItems.Count);
}
}
}
}
Usage:
<controls:MultiSelectComboBox x:Name="MultiSelectComboBox"
ItemsSource="{Binding Values}"
SelectedItems="{Binding SelectedValues, Mode=TwoWay}"
NoSelectionText="{Binding EmptySelectionString}"
MultipleSelectionTextFormat="{Binding MultipleSelectedFormatString}"
PopupHeight="500" PopupWidth="200"
HorizontalAlignment="Stretch" FontSize="14" MinHeight="26" Height="26">
<controls:MultiSelectComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</controls:MultiSelectComboBox.ItemTemplate>
</controls:MultiSelectComboBox>
Notes:
In current state can only be bound to a primitive type collection
like List< String >.
SelectedValues has to be List< object >, you can use
Cast< T > to convert from List< String >.
There is no built-in multi-select combobox in UWP, but you can build your own like this - Issue with multiselect combobox control in Windows 8 .
Basically you can add checkboxes to each item in a combobox and create a logic that will gather the selected items and provide a bindable way to access them.
To make it even simpler, you can create a special class that will have a IsChecked property and just add the checkbox with two-way binding to this property. This will ensure checking of the box in the UI will be reflected in the class and you can then just enumerate all the items to find those that have IsChecked set to true.
I would build it something like this...
First build your class
public class MultiSelectComboBox : ComboBox
{
public List<ComboBoxItem> SelectedItems
{
get { return (List<ComboBoxItem>)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(List<ComboBoxItem>), typeof(MultiSelectComboBox), new PropertyMetadata(new List<ComboBoxItem>()));
public static void SetIsSelected(UIElement element, bool value)
{
element.SetValue(IsSelectedProperty, value);
}
public static bool GetIsSelected(UIElement element)
{
return (bool)element.GetValue(IsSelectedProperty);
}
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.RegisterAttached("IsSelected", typeof(bool), typeof(ComboBoxItem), new PropertyMetadata(false, OnIsSelectedChanged));
public static void SetParentComboBox(UIElement element, MultiSelectComboBox value)
{
element.SetValue(ParentComboBoxProperty, value);
}
public static MultiSelectComboBox GetParentComboBox(UIElement element)
{
return (MultiSelectComboBox)element.GetValue(ParentComboBoxProperty);
}
public static readonly DependencyProperty ParentComboBoxProperty =
DependencyProperty.RegisterAttached("ParentComboBox", typeof(MultiSelectComboBox), typeof(MultiSelectComboBox), new PropertyMetadata(null));
protected override DependencyObject GetContainerForItemOverride()
{
ComboBoxItem comboBoxitem = new ComboBoxItem();
MultiSelectComboBox.SetParentComboBox(comboBoxitem, this);
return comboBoxitem;
}
private static void OnIsSelectedChanged(object comboBoxItem, DependencyPropertyChangedEventArgs args)
{
ComboBoxItem item = comboBoxItem as ComboBoxItem;
if (item != null)
{
MultiSelectComboBox parent = MultiSelectComboBox.GetParentComboBox(item);
if (MultiSelectComboBox.GetIsSelected(item))
{
parent.SelectedItems.Add(item);
}
else
{
parent.SelectedItems.Remove(item);
}
}
}
}
Then create your items template
<local:MultiSelectComboBox ItemsSource="{Binding Items}" SelectedItems="{Binding SelectedItems, Mode=TwoWay}">
<local:MultiSelectComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding ItemContent}" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}, Path=IsSelected, Mode=TwoWay}"/>
</DataTemplate>
</local:MultiSelectComboBox.ItemTemplate>
</local:MultiSelectComboBox>
However, this might take some massaging to get working. In the end you want to subclass Combobox and make it do some new stuff.

WP7 listbox binding not working properly

A noob error for sure (I started yesterday afternoon developing in WP7), but I'm wasting a lot time on it.
I post my class and a little part of my code:
public class ChronoLaps : INotifyPropertyChanged
{
private ObservableCollection<ChronoLap> laps = null;
public int CurrentLap
{
get { return lap; }
set
{
if (value == lap) return;
// Some code here ....
ChronoLap newlap = new ChronoLap()
{
// Some code here ...
};
Laps.Insert(0, newlap);
lap = value;
NotifyPropertyChanged("CurrentLap");
NotifyPropertyChanged("Laps");
}
}
public ObservableCollection<ChronoLap> Laps {
get { return laps; }
set
{
if (value == laps) return;
laps = value;
if (laps != null)
{
laps.CollectionChanged += delegate
{
MeanTime = Laps.Sum(p => p.Time.TotalMilliseconds) / (Laps.Count * 1000);
NotifyPropertyChanged("MeanTime");
};
}
NotifyPropertyChanged("Laps");
}
}
}
MainPage.xaml.cs
public partial class MainPage : PhoneApplicationPage
{
public ChronoLaps History { get; private set; }
private void butStart_Click(object sender, EventArgs e)
{
History = new ChronoLaps();
// History.Laps.Add(new ChronoLap() { Distance = 0 });
LayoutRoot.DataContext = History;
}
}
MainPage.xaml
<phone:PhoneApplicationPage>
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid Grid.Row="2">
<ScrollViewer Margin="-5,13,3,36" Height="758">
<ListBox Name="lbHistory" ItemContainerStyle="{StaticResource ListBoxStyle}"
ItemsSource="{Binding Laps}"
HorizontalAlignment="Left" Margin="5,25,0,0"
VerticalAlignment="Top" Width="444">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Lap}" Width="40" />
<TextBlock Text="{Binding Time}" Width="140" />
<TextBlock Text="{Binding TotalTime}" Width="140" />
<TextBlock Text="{Binding Distance}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
Problem is that when I add one or more items to History.Laps collection, my listbox is not refreshed and these items don't appear.
But if I remove comment on // History.Laps.Add(new ChronoLap()... line, this item appear and so every other inserted later.
More: if I remove that comment and then write History.Laps.Clear() (before or after setting binding) binding is not working anymore. It's like it gets crazy if collection is empty.
I really don't understand the reason...
UPDATE AND SOLUTION:
If i move
History = new ChronoLaps();
LayoutRoot.DataContext = History;
from butStart_Click to public MainPage() everything works as expected.
Can someone explain me the reason?
Actually I see no point of having a separate class for ChronoLaps. Here is how I modified the code for MainPage.xaml.cs and everything seems to be working for me.
public partial class MainPage : PhoneApplicationPage
{
public ObservableCollection<ChronoLap> Laps { get; set; }
public double MeanTime { get; set; }
// Constructor
public MainPage()
{
InitializeComponent();
Laps = new ObservableCollection<ChronoLap>();
Laps.CollectionChanged += delegate
{
MeanTime = Laps.Sum(p => p.Time.TotalMilliseconds) / (Laps.Count * 1000);
};
DataContext = this;
Loaded += (s, e) =>
{
Laps.Add(new ChronoLap() {Time = TimeSpan.FromSeconds(1000)});
Laps.Add(new ChronoLap() {Time = TimeSpan.FromSeconds(1000)});
Laps.Add(new ChronoLap() {Time = TimeSpan.FromSeconds(1000)});
};
}
}
Try binding DataContext and ItemSource for listbox..
How i have done is..
<ListBox x:Name="AppList" Background="White" DataContext="{Binding DisplayItem}" SelectionChanged="AppList_SelectionChanged" Height="500" Width="auto">
<ListBox.ItemTemplate>
<DataTemplate>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And i dont know if it will help but still i will just post the code that i am using..
ItemList.cs
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
namespace AppHouseLibrary
{
public class ItemList : IComparable
{
private string _name;
public string WidgetName
{
get
{
return _name;
}
set
{
_name = value;
}
}
public int ID
{
get;
set;
}
private BitmapImage _Icon;
public BitmapImage Icon
{
get
{
return _Icon;
}
set
{
_Icon = value;
}
}
//public string arrow { get; set; }
public BitmapImage arrow { get; set; }
public int CompareTo(ItemList other)
{
return this.WidgetName.CompareTo(other.WidgetName);
}
}
}
I have a UIManager.cs class in which am refreshing the data before i load it on the UI to the user..
using System;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Media.Imaging;
using System.Collections.Generic;
using StirLibrary.com.mportal.data.bean;
using com.mportal.utils;
using StirLibrary.com.mportal.utils;
namespace StirLibrary.com.UI
{
public class UIManager : INotifyPropertyChanged
{
private static UIManager instance = null;
private static string TAG = "UIManager";
BitmapImage arrowImage = Utils.returnImage(ImageUtils.ARROW);
public List<ItemList> data = new List<ItemList>();
public static UIManager getInstance()
{
if (instance == null)
{
instance = new UIManager();
}
return instance;
}
private ObservableCollection<ItemList> _displayItem = new ObservableCollection<ItemList>();
public ObservableCollection<ItemList> DisplayItem
{
get
{
return _displayItem;
}
}
private UIManager()
{
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String PropertyName)
{
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public WidgetBean[] serviceBeanList = null;
public WidgetBean[] wheelBeanList = null;
public WidgetBean getServiceWidgetBean(int selectedIndex)
{
try
{
if (serviceBeanList != null)
{
return serviceBeanList[selectedIndex];
}
}
catch (Exception e)
{
Logger.log(TAG, e.Message);
}
return null;
}
public WidgetBean getWheelWidgetBean(int selectedIndex)
{
try
{
if (wheelBeanList != null)
{
return wheelBeanList[selectedIndex];
}
}
catch (Exception e)
{
Logger.log(TAG, e.Message);
}
return null;
}
public void DisplayCatalog(string[] ServiceDisplayName, string[] WheelDisplayName, BitmapImage[] ServiceIcons, WidgetBean[] ServiceBeanList, WidgetBean[] WheelBeanList)
{
try
{
DisplayItem.Clear();
string disp1 = string.Empty;
string name = ServiceDisplayName[0];
wheelBeanList = WheelBeanList;
serviceBeanList = ServiceBeanList;
for (int i = 0; i < ServiceDisplayName.Length; i++)
{
WidgetBean bean = serviceBeanList[i];
if (bean.isCategory())
{
DisplayItem.Add(new ItemList { WidgetName = ServiceDisplayName[i], Icon = ServiceIcons[i], arrow = arrowImage });
}
else
{
DisplayItem.Add(new ItemList { WidgetName = ServiceDisplayName[i], Icon = ServiceIcons[i] });
}
}
NotifyPropertyChanged("UI");
}
catch (Exception e)
{
Logger.log(TAG,e.Message);
}
}
public void DisplayCatalog(string[] displayName, BitmapImage[] icons, WidgetBean[] beanArray)
{
try
{
serviceBeanList = beanArray;
DisplayItem.Clear();
for (int i = 0; i < displayName.Length; i++)
{
WidgetBean bean = serviceBeanList[i];
if (bean.isCategory())
{
DisplayItem.Add(new ItemList { WidgetName = displayName[i], Icon = icons[i], arrow = arrowImage });
}
else
{
DisplayItem.Add(new ItemList { WidgetName = displayName[i], Icon = icons[i] });
}
}
NotifyPropertyChanged("UI");
}
catch (Exception e)
{
Logger.log(TAG,e.Message);
}
}
}
}

Resources