I have an entry control in my XAML where I set the initial value on page appear through data binding. Initially the value is appearing but when I am updating it from another view model it is not getting updated on UI.
Below is the XAML code and XAML.CS
<ListView
x:Name="workList"
Grid.Row="2"
SeparatorColor="{DynamicResource AccentColor}"
ItemsSource="{ Binding WorkItems }"
Margin="5"
CachingStrategy="RecycleElement"
RowHeight="440"
SeparatorVisibility="Default"
SelectionMode="None"
HasUnevenRows="False">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<local:LoadItemPutawayTemplate />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-
compatibility/2006"
mc:Ignorable="d"
x:Class="Sanipex.LoadItemPutawayTemplate">
<Grid
RowSpacing="0"
Padding="0"
Margin="0,10,0,0"
>
<Grid.RowDefinitions>
<RowDefinition
Height="*" />
</Grid.RowDefinitions>
<Entry
x:Name="OverrideLoc"
Grid.Row="0"
TextColor="Black"
WidthRequest="110"
Text="{Binding toLocation}"
grial:EntryProperties.BorderCornerRadius="10"
grial:EntryProperties.BorderStyle="RoundRect"
grial:EntryProperties.BorderColor="Black"
HorizontalOptions="StartAndExpand"
VerticalOptions="Center"
Focused="OverrideLoc_Focused"
TextChanged="OverrideLoc_TextChanged"
grial:EntryProperties.HorizontalPadding="5"
FontAttributes="Bold"
PlaceholderColor="Black"
FontSize="20"/>
</Grid>
public partial class ItemPutAway : ContentPage
{
private static ItemPutAwayViewModel obj;
public ItemPutAway()
{
InitializeComponent();
obj = new ItemPutAwayViewModel();
BindingContext = obj;
}
public static ItemPutAwayViewModel itemPutAwayViewModel
{
get
{
return obj;
}
}
protected override async void OnAppearing()
{
obj.LoadData();
}
}
Below is my first view model code
public class ItemPutAwayViewModel : INotifyPropertyChanged
{
private IList<WorkItem> workItems;
public event PropertyChangedEventHandler PropertyChanged;
public string ltoLocation;
public string toLocation
{
get => ltoLocation;
set
{
ltoLocation = value;
OnPropertyChanged(nameof(toLocation));
}
}
public IList<WorkItem> WorkItems
{
get => workItems;
set
{
workItems = value;
OnPropertyChanged(nameof(WorkItems));
}
}
public void LoadData()
{
WorkItems = App.dataManager.GetItemPutAwayWorks();
}
public void setLocation(string _location)
{
toLocation = _location;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Below is the code through which I am trying to update the toLocation binding value to different value from another XAML page as below:
public partial class AvailableLocationsPopUp : PopupPage
{
private static AvailableLocationViewModel obj;
public AvailableLocationsPopUp(WorkItem _workItem)
{
InitializeComponent();
obj = new AvailableLocationViewModel(gWorkItem.itemid);
BindingContext = obj;
}
private void OnClose(object sender, EventArgs e)
{
PopupNavigation.Instance.PopAsync();
}
private void ListView_ItemTapped(object sender, ItemTappedEventArgs e)
{
Location content = e.Item as Location;
ItemPutAway.itemPutAwayViewModel.setLocation("ABC-XYZ");
PopupNavigation.Instance.PopAsync();
}
}
As I mentioned in the discussion, you have to also implement the INotifyPropertyChanged interface of the class WorkItem.
Implement INotifyPropertyChanged in ItemPutAwayViewModel will only help for changes in the WorkItems(like add or remove one WorkItem), not the changes inside the WorkItem.
So, the code should be:
public class WorkItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _toLocation;
public string toLocation
{
get => _toLocation;
set
{
_toLocation = value;
NotifyPropertyChanged();
}
}
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Related
I have a page
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:PassSystem.Controls;assembly=PassSystem"
x:Class="PassSystem.Views.CreatePassPage"
Title="Оформление пропуска">
<ScrollView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout>
<FlexLayout Direction="Column" BackgroundColor="White">
<controls:ActionOption Title="Название" Value="1" LeftMargin="18">
</controls:ActionOption>
</FlexLayout>
</StackLayout>
</ScrollView>
</ContentPage>
and i have contentview
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:PassSystem.Controls;assembly=PassSystem"
xmlns:effects="clr-namespace:PassSystem.Effects;assembly=PassSystem"
xmlns:local="clr-namespace:PassSystem"
x:Class="PassSystem.Controls.ActionOption">
<FlexLayout x:Name="MainLayout" Direction="Row" JustifyContent="Center" AlignContent="Center" AlignItems="Center" HeightRequest="52"
effects:ClickableEffect.ClickCommand="!!"
effects:ClickableEffect.CancelCommand="{Binding UpCommand}"
effects:ClickableEffect.UpCommand="{Binding UpCommand}"
effects:ClickableEffect.DownCommand="{Binding DownCommand}">
<Label Text="{Binding Title}"
TextColor="{StaticResource PrimaryTextColor}"
FontSize="15"
VerticalTextAlignment="Center"
VerticalOptions="FillAndExpand"
FlexLayout.Grow="1"
Margin="{Binding TitleMargin, Mode=TwoWay}">
</Label>
<StackLayout Orientation="Horizontal" HeightRequest="16" VerticalOptions="CenterAndExpand" Margin="0,0,8,0">
<Label Text="{Binding Value}" FontSize="12" TextColor="{StaticResource SecondaryTextColor}" VerticalTextAlignment="Center"/>
<controls:IconView Source="forward" HeightRequest="14" WidthRequest="14" ForegroundColor="{StaticResource AdditionalTextColor}"/>
</StackLayout>
<FlexLayout.Effects>
<effects:ClickableEffect />
</FlexLayout.Effects>
</FlexLayout>
</ContentView>
.cs file
public partial class ActionOption : ContentView
{
public ActionOption()
{
InitializeComponent();
BindingContext = this;
}
private string _title;
public string Title
{
get => _title;
set
{
if(value == _title) return;
_title = value;
OnPropertyChanged();
}
}
private string _value;
public string Value
{
get => _value;
set
{
if (value == _value) return;
_value = value;
OnPropertyChanged();
}
}
public Thickness TitleMargin => new Thickness(_leftMargin, 0, 6, 0);
private double _leftMargin;
public double LeftMargin
{
get => _leftMargin;
set
{
if (Math.Abs(value - _leftMargin) < 0.01d) return;
_leftMargin = value;
OnPropertyChanged();
OnPropertyChanged(nameof(TitleMargin));
}
}
private ICommand _clicked;
public ICommand Clicked
{
get => _clicked;
set
{
if (value == _clicked) return;
_clicked = value;
OnPropertyChanged();
}
}
public ICommand UpCommand => new Command(() => MainLayout.BackgroundColor = Color.FromHex("#fff"));
public ICommand DownCommand => new Command(() => MainLayout.BackgroundColor = (Color)((App)Application.Current).Resources["HighlightingColor"]);
}
And I need to bind the ClickableEffect.ClickCommand from the page. I.e.
<controls:ActionOption Title="TitleHere" Value="1" LeftMargin="18" Clicked="{Binding ClickedCommand}">
</controls:ActionOption>
And in control
`effects:ClickableEffect.ClickCommand="{Binding ClickCommand (FromPage)}"`
Additional information. ClickableEffect
{
public ClickableEffect() : base("PassSystem.ClickableEffect")
{
}
#region Click
public static readonly BindableProperty ClickCommandProperty = BindableProperty
.CreateAttached("ClickCommand", typeof(ICommand), typeof(ClickableEffect), (object)null);
public static ICommand GetClickCommand(BindableObject view)
{
return (ICommand)view.GetValue(ClickCommandProperty);
}
public static void SetClickCommand(BindableObject view, ICommand value)
{
view.SetValue(ClickCommandProperty, value);
}
public static readonly BindableProperty ClickCommandParameterProperty = BindableProperty
.CreateAttached("ClickCommandParameter", typeof(object), typeof(ClickableEffect), (object)null);
public static object GetClickCommandParameter(BindableObject view)
{
return view.GetValue(ClickCommandParameterProperty);
}
public static void SetClickCommandParameter(BindableObject view, object value)
{
view.SetValue(ClickCommandParameterProperty, value);
}
#endregion
}
Implementation of Clickable Effect on Android:
public class ClickableListener : Java.Lang.Object, View.IOnTouchListener, View.IOnClickListener, View.IOnLongClickListener
{
private Element Element { get; }
private View View { get; }
public ClickableListener(Element element, View view)
{
Element = element;
View = view;
}
...
public void OnClick(View v)
{
Tap();
}
private void Tap()
{
var command = ClickableEffect.GetClickCommand(Element);
var parameter = ClickableEffect.GetClickCommandParameter(Element);
command?.Execute(parameter);
}
}
[assembly: ResolutionGroupName("PassSystem")]
[assembly: ExportEffect(typeof(AndroidClickableEffect), "ClickableEffect")]
namespace PassSystem.Droid.Native.Effects
{
public class AndroidClickableEffect : PlatformEffect
{
private bool _attached;
protected override void OnAttached()
{
//because an effect can be detached immediately after attached (happens in listview), only attach the handler one time.
if(!_attached)
{
var control = Control ?? Container;
var listener = new ClickableListener(Element, control);
control.SetOnClickListener(listener);
...
_attached = true;
}
}
}
How to implement this? Thank you.
P.S. It's hard for me to explain and I hope you understand. :)
In order to achieve the desired behaviour:
ClickCommand have to be an attached property of a ClickableEffect. More information can be found in the official doc: Passing Effect Parameters as Attached Properties.
Since you have a ContentView in between the effect and the page, you have to make sure that BindindContext of the ContentView is set correctly. I am afraid that you will have to define a ClickCommand bindable property on the ContentView level and bind it to the effect's command. So: Page.BindingContext.ClickCommand => ContentView.ClickCommand => ClickableEffect.ClickCommand (where => is binding).
Which basically raise a question - why do you need an effect?
I have a very simple form that I'm using to experiment with BindableProperty. Here's the XAML for the form
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyBindableProperty"
x:Class="MyBindableProperty.MainPage">
<StackLayout>
<local:MyLabel x:Name="BindingLabel" Text="{Binding Text}" MyText="{Binding Text}"
VerticalOptions="Center"
HorizontalOptions="Center" />
<Entry x:Name="BindingEntry" Text="{Binding Text, Mode=TwoWay}"/>
<Entry x:Name="BindingEntry2" Text="{Binding Text, Mode=TwoWay}"/>
<Button x:Name="BindingButton" Text="Reset"/>
</StackLayout>
</ContentPage>
And here is the code behind
public partial class MainPage : ContentPage
{
public DataSourceClass DataSourceObject { get; set; }
public MainPage()
{
DataSourceObject = new DataSourceClass { Text = "Test1" };
BindingContext = DataSourceObject;
InitializeComponent();
BindingButton.Clicked += BindingButton_Clicked;
}
private void BindingButton_Clicked(object sender, EventArgs e)
{
var boundText = this.BindingLabel.Text;
var boundMyText = this.BindingLabel.MyText;
DataSourceObject.Text = "Test2";
}
}
Finally, here is the custom label class used in the XAML -
public class MyLabel : Label
{
public string MyText
{
get
{
return (string)GetValue(MyTextProperty);
}
set
{
SetValue(MyTextProperty, value);
}
}
public static readonly BindableProperty MyTextProperty = BindableProperty.Create(nameof(MyText), typeof(string), typeof(MyLabel), "Test", BindingMode.TwoWay, propertyChanged: MyTextChanged);
public static void MyTextChanged(BindableObject bindable, object oldValue, object newValue)
{
((MyLabel)bindable).TextChanged(newValue.ToString());
}
public void TextChanged(string newText)
{
Device.BeginInvokeOnMainThread(() => this.Text = newText);
}
}
The issues I'm having are
when the page initialises the MyTextChanged handler fires, but not after any subsequent changes
when the Reset button is clicked the value in DataSourceObject.Text is correctly updated with the value from the Entry element
no matter how I try to set the values of BindingLabel and BindingEntry2 they never reflect the values of DataSourceObject.Text after the page has loaded.
Any help would be greatly appreciated.
I stumbled across this so I updated the DataSourceClass from this
public class DataSourceClass
{
public string Text { get; set; }
}
to this
public class DataSourceClass : INotifyPropertyChanged
{
private string _text;
public string Text
{
get
{
return _text;
}
set
{
_text = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
and now everything works.
I thought BindableProperty was meant to supersede INotifyPropertyChanged?
So I'm trying to implement a ListBoxItem that has a TextBlock that binds to a property in the viewmodel. But for some reason the view isn't being updated even after the viewmodel property is updated. Can anyone give me some pointers?
In the view (xaml)
<phone:Pivot HorizontalAlignment="Left" Height="748" Margin="10,10,0,0" Grid.Row="1" Title="pivot" VerticalAlignment="Top" Width="460">
<StackPanel x:Name="POIStackPanel" DataContext="{StaticResource POIViewModel}">
<ListBoxItem x:Name="SelectedPOIItem">
<TextBlock Text="{Binding SelectedPickupPOI.label}" />
</ListBoxItem>
phone:PivotItem x:Name="GooglePivot" Header="Google">
<ListBox x:Name="GooglePOIList"
ItemsSource="{Binding GooglePOICollection}"
HorizontalAlignment="Left"
Height="441"
Width="436"
SelectedItem="{Binding SelectedPickupPOI, Mode=TwoWay}"
ItemTemplate="{StaticResource POIItemTemplate}" />
</phone:PivotItem>
</StackPanel>
</phone:Pivot>
In the viewmodel
public ObservableCollection<PointOfInterest> GooglePOICollection { get; private set; }
public PointOfInterest SelectedPickupPOI
{
get
{
return _selectedPickupPOI;
}
set
{
_selectedPickupPOI = value;
NotifyPropertyChanged("SelectedPickupPOI");
}
}
public PointOfInterestViewModel()
{
this.GooglePOICollection = = new ObservableCollection<PointOfInterest>();
this.SelectedPickupPOI = new PointOfInterest();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
My PointOfInterest Models
public abstract class PointOfInterest : INotifyPropertyChanged
{
public abstract string id { get; set; }
public abstract string label { get; set; }
}
public class GooglePOI : PointOfInterest
{
private string _label;
public override string label
{
get { return _label; }
set
{
_label = value;
NotifyPropertyChanged("label");
}
}
}
Update
so I found out that if I do _selectedPickupPOI.label = value.label; it will update the view correctly. What am I doing wrong?
I have ObservableCollection and value that need to find the item in the collection. any ideas? (p.s converter not good idea, because i have many collections)
This functionality (applying a filter) belongs into the ViewModel. Here is an easy example for illustration.
You might also want to look at the CollectionViewSource for a more refined version of the same concept.
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" >
<ListBox ItemsSource="{Binding MyClasses}" DisplayMemberPath="Name" Margin="5" />
<ListBox ItemsSource="{Binding MyFilteredClasses}" DisplayMemberPath="Name" Margin="5" />
<TextBox Text="{Binding MySelectedClass.Name}" Margin="5" />
</StackPanel>
</Window>
ViewModel:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
namespace WpfApplication1
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<Class1> _myClasses;
public ObservableCollection<Class1> MyClasses { get { return _myClasses; } set { _myClasses = value; OnPropertyChanged("MyClasses"); } }
private List<Class1> _myFilteredClasses;
public List<Class1> MyFilteredClasses { get { return _myFilteredClasses; } set { _myFilteredClasses = value; OnPropertyChanged("MyFilteredClasses"); } }
private Class1 _mySelectedClass;
public Class1 MySelectedClass { get { return _mySelectedClass; } set { _mySelectedClass = value; OnPropertyChanged("MySelectedClass"); } }
public ViewModel()
{
MyClasses = new ObservableCollection<Class1>()
{
new Class1() { Name = "Connelly" },
new Class1() { Name = "Donnelly" },
new Class1() { Name = "Fonnelly" },
new Class1() { Name = "McGregor" },
new Class1() { Name = "Griffiths" }
};
// filter your ObservableCollection by some criteria, and bind to the result (either another list, or just one item)
MyFilteredClasses = MyClasses.Where(c => c.Name.EndsWith("onnelly")).ToList();
MySelectedClass = MyClasses.FirstOrDefault(c => c.Name.StartsWith("Mc"));
}
}
public class Class1 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _name;
public string Name { get { return _name; } set { _name = value; OnPropertyChanged("Name"); } }
}
}
I'm trying to populate a list view control on a XAML page in a Win8 application. I've added the following attributes to the page XAML:
<common:LayoutAwarePage x:Class="SecAviTools.ViewWeatherHome"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:common="using:MyNameSpace.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:MyNameSpace"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="using:MyNameSpace.Win8ViewModel"
x:Name="pageRoot"
DataContext="{Binding DefaultViewModel,
RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<!-- ... -->
<ListView ItemsSource="{Binding Path=viewmodel:Stations}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Id}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My source class is:
namespace MyNameSpace.Win8ViewModel
{
public class Stations : INotifyPropertyChanged, INotifyCollectionChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected void OnCollectionChanged<T>(NotifyCollectionChangedAction action, ObservableCollection<T> items)
{
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, items));
}
public Stations()
{
AllStations = new ObservableCollection<Station>();
AddStations(new List<Station>());
}
public ObservableCollection<Station> AllStations { get; private set; }
public void AddStations(List<Station> stations)
{
AllStations.Clear();
foreach (var station in stations)
AllStations.Add(station);
OnCollectionChanged(NotifyCollectionChangedAction.Reset, AllStations);
OnPropertyChanged("AllStations");
}
}
public class Station
{
public int Id { get; set; }
public string Name { get; set; }
}
}
There is also a button on the page (not shown here) that does the following:
public sealed partial class MyPage : MyNameSpace.Common.LayoutAwarePage
{
private Stations m_Stations = new Stations();
//...
private async void SearchButtonClick(object sender, RoutedEventArgs e)
{
var list = new List<Station>();
list.Add(new Station() { Id = 0, Name = "Zero" });
list.Add(new Station() { Id = 1, Name = "One" });
m_Stations.AddStations(list);
}
}
However, when I run the code, nothing appears in the list view. What am I missing?
TIA
You don't show what DefaultViewModel is, but I'll assume it's set to be an instance of the class you show, Stations. In that case, you need to binding to be:
<ListView ItemsSource="{Binding Path=AllStations}">
The Path of a binding usually refers to a property somewhere; with no further specification, such as a Source, it's a property on the object that is set to be the DataContext.
Regardless, you don't need the namespace qualifier "viewmodel:".
As an aside, if you do end up binding to the ObservableCollection, you don't need to implement INotifyCollectionChanged, only INotifyPropertyChanged for when the AllStations property is set.