Xamarin: Bind picker selected item with FreshMvvM ViewModel - xamarin

I'm creating a form where a product status needs to be selected with a dropdown menu.
I've created a picker for this. The data is loaded from a list in my ViewModel, but it doesn't get sent back.
I've tried the same using entry fields and that works fine. I just have no idea how to link the picker with the view model.
Here's my code.
Xaml
</Grid>
<Label Text="Current status" Style="{StaticResource MainLabel}"/>
<Label Style="{StaticResource MainLabel}" Text="{Binding ProductionStatus, Mode=TwoWay}"/>
<!-- gets send when saved-->
<Entry Style="{StaticResource MainEntry}" Text="{Binding ProductionStatus, Mode=TwoWay}" Keyboard="Text" />
<Label Text="Remark" Style="{StaticResource MainLabel} "/>
<!-- gets send when saved-->
<Entry Text="{Binding Remark}" Keyboard="Text" Style="{StaticResource MainEntry}"/>
<!-- Does not bind with anything.-->
<Picker x:Name="statusPicker" ItemsSource="{Binding ProductionOrderStatusName}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding ProductionStatusName}"/>
<Button Style="{StaticResource PrimaryButton}" Text="Save" Command="{Binding SaveCommand}"></Button>
Code-behind ViewModel
public ICommand SaveCommand
{
get
{
return new Command(ExecuteSaveCommand);
}
}
private async void ExecuteSaveCommand()
{
int statusId = FindProductionOrderStatusId(ProductionStatus); //the production status should be the result of the selected value in the picker
Guid id = _CurrentProductionOrder.Id;
string remark = Remark; // value from the remark entery in the xaml
await __productionOrderService.UpdateAsync(id, remark,statusId);
}
Properties
public ObservableCollection<ProductionOrderStatus> productionOrderStatusName;
public ObservableCollection<ProductionOrderStatus> ProductionOrderStatusName
{
get { return productionOrderStatusName; }
set
{
productionOrderStatusName = value;
}
}
public int amount;
public int Amount
{
get { return amount; }
set
{
amount = value;
}
}
public DateTime finishDate;
public DateTime FinishDate
{
get { return finishDate; }
set
{
finishDate = value;
}
}
public int ordernumber;
public int OrderNumber
{
get { return ordernumber; }
set
{
ordernumber = value;
}
}
public string remark;
public string Remark
{
get { return remark; }
set
{
remark = value;
}
}
public string productionStatus;
public string ProductionStatus
{
get
{
return productionStatus;
}
set
{
productionStatus = value;
}
}
private string materialNumber;
public string MaterialNumber
{
get { return materialNumber; }
set
{
materialNumber = value;
}
}
private string materialDescription;
public string MaterialDescription
{
get { return materialDescription; }
set
{
materialDescription = value;
}
}
Code behind loading my data
public OrderViewModel()
{
_productionOrderStepService = new MockingProductionOrderStepService();
_materialService = new MockingMaterialService();
__productionOrderService = new MockingProductionOrderService();
__productionOrderStatusService = new MockingProductionOrderStatusService();
_seederService = new Seeder(__productionOrderService, _productionOrderStepService, __productionOrderStatusService, _materialService);
_seederService.EnsureSeeded();
}
public override void Init(object initData)
{
_CurrentProductionOrder = initData as ProductionOrder;
LoadItemState();
base.Init(initData);
}
private void LoadItemState()
{
ObservableCollection<ProductionOrderStatus> ProductionStatusName = new ObservableCollection<ProductionOrderStatus>(__productionOrderStatusService.GetListAllAsync().Result);
this.ProductionOrderStatusName = ProductionStatusName;
this.materialDescription = FindMaterialDescription(_CurrentProductionOrder.MaterialId);
this.materialNumber = FindMaterialNumber(_CurrentProductionOrder.MaterialId);
this.productionStatus = FindProductionOrderStatus(_CurrentProductionOrder.StatusId);
this.remark = _CurrentProductionOrder.Remark;
this.amount=_CurrentProductionOrder.Amount;
this.finishDate = _CurrentProductionOrder.FinishDate;
this.ordernumber = _CurrentProductionOrder.OrderNumber;
}
Thx for the help!

you are making this more complicated than it needs to be
<Picker x:Name="statusPicker"
// this is the List of items X to display
ItemsSource="{Binding ProductionOrderStatusName}"
// this tells the picker which property of X to display to the user
ItemDisplayBinding="{Binding Name}"
// this is the specific X the user has selected
SelectedItem="{Binding SelectedStatus}" />
then in your VM
public ObservableCollection<ProductionOrderStatus> ProductionOrderStatusName { get; set; }
public ProductionOrderStatus SelectedStatus { get; set; }

Related

Login MVVM / Labels

i have a question regarding to Binding. I have a Login that I now decided to transfer to MVVM until now i had labels with content "wrong password" and such in code behind and i was setting their visibility. Btw I do have RESX file and I am binding my labels, just not sure how to approach this.
bool isPreparedToBeSubmitted = true;
if (string.IsNullOrEmpty(UserName))
{
isPreparedToBeSubmitted = false;
emailValLabel.IsVisible = true;
}
if (string.IsNullOrEmpty(Password))
{
isPreparedToBeSubmitted = false;
passwordEntryValLabel.IsVisible = true;
}
But I think this can be done much nicer, but I am not sure how. Do i get the visibility bool in Model and then set binding to the label in View?
Since you want to use MVVM. It would be better to bind the text of entry to ViewModel . Don't forget to set the binding mode as OneWayToSource .
You could check the following demo . The page contains two entries . The submit button will appear when user both input the user name and password .
in xaml
<StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
<Entry Placeholder="input username" Text="{Binding UserName,Mode=OneWayToSource}" HeightRequest="40" WidthRequest="200" />
<Entry Placeholder="input password" Text="{Binding Password,Mode=OneWayToSource}" HeightRequest="40" WidthRequest="200" />
<Button Text="Submit" IsVisible="{Binding IsVisible}" Command="{Binding SubmitCommand}" />
</StackLayout>
in ViewModel
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ICommand SubmitCommand { get; set; }
//IsVisible
bool isVisible;
public bool IsVisible
{
get
{
return isVisible;
}
set
{
if (isVisible != value)
{
isVisible = value;
NotifyPropertyChanged("IsVisible");
}
}
}
string userName;
public string UserName
{
get
{
return userName;
}
set
{
if (userName != value)
{
userName = value;
IsSubmitVisible();
NotifyPropertyChanged("UserName");
}
}
}
string password;
public string Password
{
get
{
return password;
}
set
{
if (password != value)
{
password = value;
IsSubmitVisible();
NotifyPropertyChanged("Password");
}
}
}
void IsSubmitVisible()
{
if (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password))
{
IsVisible = true;
}
else
{
IsVisible = false;
}
}
public MyViewModel()
{
SubmitCommand = new Command(()=> {
//...
});
}
}

Update list view on expand

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 :)

UWP: exception when setting a property just after setting another one

I think I got a threading problem in my UWP app.
I want to do a very simple thing:
a UI with 2 numeric fields;
if a numeric value is typed in field1, I want field2 to be set with a ratio of field1 (example: field2 = ratio * field1).
I am using x:Bind and TextChanging events. For unknown reasons, I wasn't able in the XAML to "call" the TextChanging event without having an exception at startup. Therefore, I am using the Loaded event.
Here's my model class, simply called MyModel:
public class MyModel : INotifyPropertyChanged
{
private readonly uint r1 = 3;
private uint _field1;
public uint Field1
{
get { return this._field1; }
set
{
this.Set(ref this._field1, value);
if (value == 0)
{
Field2 = 0;
}
else
{
Field2 = value * r1;
}
}
}
private uint _field2;
public uint Field2
{
get { return this._field2; }
set
{
this.Set(ref this._field2, value);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisedPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
else
{
storage = value;
this.RaisedPropertyChanged(propertyName);
return true;
}
}
}
My ViewModel:
public class MyModelViewModel : INotifyPropertyChanged
{
public MyModel MyModel { get; set; }
public MyModelViewModel()
{
// Initialisation de notre page
this.MyModel = new MyModel()
{
Field1 = 0
};
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
my code behind (I'm filtering the input to avoid a cast exception):
public sealed partial class MainPage : Page
{
public MyModelViewModel ViewModel { get; set; } = new MyModelViewModel();
public MainPage()
{
this.InitializeComponent();
}
private void InitField1(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
field1.TextChanging += field1_TextChanging;
}
private void InitField2(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
field2.TextChanging += field2_TextChanging;
}
private void field1_TextChanging(TextBox sender, TextBoxTextChangingEventArgs args)
{
var error = errorTextBlock;
error.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
Regex regex = new Regex("[^0-9]+"); // All but numeric
if (regex.IsMatch(sender.Text))
{
error.Text = "Non numeric char";
error.Visibility = Windows.UI.Xaml.Visibility.Visible;
sender.Text = this.ViewModel.MyModel.Field1.ToString();
}
else
{
this.ViewModel.MyModel.Field1 = Convert.ToUInt32(sender.Text);
}
}
private void field2_TextChanging(TextBox sender, TextBoxTextChangingEventArgs args)
{
var error = errorTextBlock;
error.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
Regex regex = new Regex("[^0-9]+");
if (regex.IsMatch(sender.Text))
{
error.Text = "Non numeric char";
error.Visibility = Windows.UI.Xaml.Visibility.Visible;
sender.Text = this.ViewModel.MyModel.Field2.ToString();
}
else
{
this.ViewModel.MyModel.Field2 = Convert.ToUInt32(sender.Text);
}
}
}
Finally, my XAML:
<TextBlock Grid.Row="0" Grid.Column="0" x:Name="errorTextBlock" Text="" Visibility="Collapsed" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Field 1" />
<TextBox Grid.Row="1" Grid.Column="1" x:Name="field1" Text="{x:Bind ViewModel.MyModel.Field1, Mode=OneWay}" Loaded="InitField1" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Field 2" />
<TextBox Grid.Row="2" Grid.Column="1" x:Name="field2" Text="{x:Bind ViewModel.MyModel.Field2, Mode=OneWay}" Loaded="InitField2" />
At runtime, if I type a non numeric char in field1, the input is filtered, field1 returns to its previous value without the screen "blinking" (that's why I use the TextChanging event and not the TextChanged). Perfect! But if I type a numeric char, field1 is correctly updated (I can see that with breakpoint), but when field2 is set, I got a native exception when RaisedPropertyChanged is called:
I'm suspecting some kind of threading error, but I'm pretty new to this kind of development. Any idea? Thanks!
Updated to use a separate 'Model' class
Here's how you can create a text box that when a number (integer) is entered into it another text box shows the entered number multiplied by another number.
Here's the UI. Note the Mode used for each binding and the second textbox is readonly because that's just for display.
<StackPanel>
<TextBlock Text="Value 1" />
<TextBox Text="{x:Bind ViewModel.MyModel.Value1, Mode=TwoWay}" />
<TextBlock Text="Value 2" />
<TextBox Text="{x:Bind ViewModel.MyModel.Value2, Mode=OneWay}" IsReadOnly="True" />
</StackPanel>
On the page I declare my Model
public MyViewModel ViewModel { get; set; } = new MyViewModel();
My ViewModel is very simple
public class MyViewModel
{
public MyModel MyModel { get; set; } = new MyModel();
}
The Model class contains the logic
public class MyModel : INotifyPropertyChanged
{
private string _value1;
public string Value1
{
get { return _value1; }
set
{
if (_value1 != value)
{
_value1 = value;
// Cause the updated value to be displayed on the UI
OnPropertyChanged(nameof(Value1));
// Is the entered value a number (int)?
int numericValue;
if (int.TryParse(value, out numericValue))
{
// It's a number so set the other value
// multiplied by the ratio
Value2 = (numericValue * 3).ToString();
}
else
{
// A number wasn't entered so indicate this
Value2 = "NaN";
}
// Cause the updated value2 to be displayed
OnPropertyChanged(nameof(Value2));
}
}
}
// We can use the automatic property here as don't need any logic
// relating the getting or setting this property
public string Value2 { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
With the above, when a number is entered for Value1 then Value2 will show a number three times as much (because I've set the ratio of 3).
You may notice that if you try the above that the change doesn't happen immediately and Value2 is only updated when the focus leaves the Value1 text box. This is because, by default, the two-way binding is only updated when focus is lost. This can easily be changed though.
If instead of using the new x:Bind method of binding we use the traditional Binding method we can force the binding to be updated whenever we want. Say, when the text is changed.
Modify the TextBox declaration like this:
<TextBox Text="{Binding ViewModel.Value1, Mode=TwoWay}"
TextChanged="TextBox_OnTextChanged" />
Note that the binding syntax is different and we've added an event.
The handler of the event is
private void TextBox_OnTextChanged(object sender, TextChangedEventArgs e)
{
var be = (sender as TextBox).GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
This forces the binding to update but there's another change we must make as well.
With the x:Bind syntax it tries to bind to the page. With the older Binding syntax it binds to the DataContext of the page. To make these the same, update the page constructor like this
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
Now the app will work again and Value2 will be updated after every key press in the Value1 text box.

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.

ListBox doesn't refresh after I delete an item

This is my class:
[Table]
public class ListData : INotifyPropertyChanged, INotifyPropertyChanging
{
private int _id;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int Id
{
get
{
return _id;
}
set
{
if (_id != value)
{
OnPropertyChanging("Id");
_id = value;
OnPropertyChanged("Id");
}
}
}
private string _name;
[Column()]
public string Name
{
get
{ return _name; }
set
{
if (_name != value)
{
OnPropertyChanging("Name");
_name = value;
OnPropertyChanged("Name");
}
}
}
private string _url;
[Column()]
public string Url
{
get
{
return _url;
}
set
{
if (_url != value)
{
OnPropertyChanging("Url");
_url = value;
OnPropertyChanged("Url");
}
}
}
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
private void OnPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
}
public class ListDataContext : DataContext
{
public static string DBConnectionString = "Data Source=isostore:/ListDataDB.sdf";
public ListDataContext(string sConnectionString)
: base(sConnectionString)
{ }
public Table<ListData> Datas;
}
this is part of my xaml (listbox):
<ListBox x:Name="listData" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding Path=Name}"
Style="{StaticResource PhoneTextTitle2Style}"
x:Name="txbName"
Tap="txbName_Tap">
</TextBlock>
<Button Tag="{Binding BindsDirectlyToSource=True}"
Click="Button_Click"
Content="X"
BorderBrush="Red"
Background="Red"
Foreground="{StaticResource PhoneBackgroundBrush}"
VerticalAlignment="Top" BorderThickness="0" Width="70" Height="70">
</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
here is my methods for loading data:
public void LoadData()
{
this.listData.ItemsSource = GetData();
}
public ObservableCollection<ListData> GetData()
{
List<ListData> listDatas = new List<ListData>();
using (var db = new ListDataContext(ConnectionString))
{
var query = from e in db.Datas
select e;
listDatas = query.ToList();
}
return new ObservableCollection<ListData>(listDatas);
}
and this is how I delete them:
private void Button_Click(object sender, RoutedEventArgs e)
{
try
{
var data = (ListData)((Button)sender).Tag;
using (var db = new ListDataContext(ConnectionString))
{
var entities = from i in db.Datas
where i.Name == data.Name
select i;
var entity = entities.FirstOrDefault();
db.Datas.DeleteOnSubmit(entity);
db.SubmitChanges();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
What´s the problem? Why isn't my ListBox refreshing after deleting? Is there some problem with ObservableCollection?
You are removing it from the database, but I don't see where you call GetData() again, or remove it from the ObservableCollection. Try calling GetData after you delete, or at least manually remove the selected item from the collection.

Resources