Stacklayout backgroundColor binding with MVVM - xaml

I'm attempting to get my head around MVVM with XamarinForms and I'm slightly confused with regards to proper partitioning of functionality:
I have a main page, MainPage.xaml, which includes a stacklayout:
<StackLayout x:Name="MainPageStackLayout">
...
</StackLayout>
Within this stacklayout I have Picker which is bound as follows:
<Picker Title="Select a background colour"
TitleColor="Black"
TextColor="Black"
ItemsSource="{Binding MyColours}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding selectedBackGroundColour}" SelectedIndexChanged="BackGroundColourPicker_SelectedIndexChanged"/>
Following the article from microsoft (https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/userinterface-monkeyapppicker/):
I have a "View" which basically defines the layout of my page.
A "ViewModel" which holds an IList "MyColours" and a variable "SelectedBackGroundColour".
A "Model" which defines the MyColour class. A MyColour has a string name and a Xamarin.Forms.Color (from a hex value, both populated on start up).
This all works fine. I can start up the app and the Picker populates with the colours I add to "MyColours". If I change the index then my SelectedBackGroundColour also updates, has the correct name and a different RGB value.
However, I'm lost as to where I would tie in the updating of the actual background colour of the MainPageStackLayout. The View (MainPage.xaml.cs) picks up the "BackGroundColourPicker_SelectedIndexChanged" event but what is the standard practice for reading from the view model (where SelectedBackGround colour is actual defined ?)
I have a feeling I can bind Background colour in the MainPageStackLayout xaml view so I wont have to catch the selected index change event.
Thanks all.

According to your description, I guess that you want to change MainPage StackLayout BackGround color by Picker value, am I right?
If yes, please follow the steps below.
Firstly, please confirm that you implement INotifyPropertyChanged interface to notify SelectedBackGroundColour changed.
Then there are full code, please take a look:
<StackLayout x:Name="MainPageStacklayout" BackgroundColor="{Binding selectedBackGroundColour.color}">
<Picker
x:Name="picker1"
Title="Select a background colour"
ItemDisplayBinding="{Binding name}"
ItemsSource="{Binding MyColours}"
SelectedItem="{Binding selectedBackGroundColour}"
TextColor="Black"
TitleColor="Black" />
</StackLayout>
public partial class Page5 : ContentPage, INotifyPropertyChanged
{
public ObservableCollection<MyColour> MyColours { get; set; }
private MyColour _selectedBackGroundColour;
public MyColour selectedBackGroundColour
{
get { return _selectedBackGroundColour; }
set
{
_selectedBackGroundColour = value;
RaisePropertyChanged("selectedBackGroundColour");
}
}
public Page5()
{
InitializeComponent();
MyColours = new ObservableCollection<MyColour>()
{
new MyColour(){name="red",color=Color.Red},
new MyColour(){name="gray",color=Color.Gray},
new MyColour(){name="BlueViolet",color=Color.BlueViolet}
};
selectedBackGroundColour = MyColours[0];
this.BindingContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MyColour
{
public string name { get; set; }
public Color color { get; set; }
}
The screenshot:

Related

Databind properties of a class in ContentView

I'm trying to achieve what I think is probably quite simple but as I'm new to Xamarin & Databinding I think I'm getting in a spin.
I have a very simple ContentPage that just has a Databinding to my viewModel for this page and my ContentView, TotalsTemplate.
<ContentPage.BindingContext>
<vm:DealsTodayViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<template:TotalsTemplate></template:TotalsTemplate>
</StackLayout>
</ContentPage.Content>
My viewmodel has a public property of my class, Totals, which has basic int,string,decimal props.
public class DealsTodayViewModel
{
Public string ViewModelPeriod;
public PeriodTotals Totals;
public DealsTodayViewModel()
{
ViewModelPeriod = "TODAY";
Totals = new PeriodTotals
{
Period = "DAILY",
ClientServices_Deals_Chicago = 1,
ClientServices_Deals_Manchester_Na = 1,
ClientServices_Deals_Manchester_Uk = 1,
ClientServices_Ramp_Chicago = 1.2m,
ClientServices_Ramp_Manchester_Na = 1.3m,
ClientServices_Ramp_Manchester_Uk = 1.4m
};
}
}
Now in my TotalsTemplte ContentView I have a Grid with following inside.
<Label Text="{***Binding ViewModelPeriod***}" FontAttributes="Bold"/>
<Frame OutlineColor="Black" Grid.Row="0" Grid.Column="1">
<Label Text="{Binding ***Totals.Period***}" FontAttributes="Bold"/>
</Frame>
My String property on the DealsTodayViewModel is visible in my ContentView but not the Perod property from inside my Totals property, am I binding incorrectly to this?
From the document, data-binding should binding between properties instead of field:
Data binding is the technique of linking properties of two objects so
that changes in one property are automatically reflected in the other
property. Data binding is an integral part of the Model-View-ViewModel
(MVVM) application architecture.
So the solution is change the fields in your vm to properties:
public class DealsTodayViewModel
{
public string ViewModelPeriod { get; set; }
public PeriodTotals Totals { get; set; }
public DealsTodayViewModel()
{
...
}
}
Refer: field and property

Xamarin.Forms (XAML): Different layouts depending on a condition

Is there a way to choose what layout initialize depending on one condition? I have a Grid for football stats but if myViewModel.Sport == Sports.Basketball I'd like to load a completely different layout.
I tried something like this with Datatrigger in each View but it seems a mess for me:
<Label Text="{Binding Goals}"
Grid.Row="1" Grid.Column="0">
<Label.Triggers>
<DataTrigger TargetType="Label"
Binding="{Binding Sport}"
Value="1">
<Setter Property="Text"
Value="{Binding Points}"/>
</DataTrigger>
</Label.Triggers>
</Label>
I show "goals" but if the Sports enum value is 1 (Sports.Basketball) I change to "points". I want to do this with lots of Labels and even Images so I need a proper way to do it.
Could someone help me? I need to load a different Grid depending on the Sport Property of my ViewModel.
Another thing you could do is place each separate sport into it's own view, add all the views to your page and set their IsVisible property depending on which sport you want to show.
An example would look like this in pseudo-code:
<Page>
<Grid>
<BasketballView IsVisible="{Binding IsBasketball}">
<SoccerView IsVisible="{Binding IsSoccer}">
<FootballView IsVisible="{Binding IsFootball}">
</Grid>
</Page>
Then set the appropriate boolean values from the ViewModel.
To use DataTemplateSelector to solve this, as mentioned by #StephaneDelcroix, you'll want a custom class that has ItemsSource and ItemTemplate properties.
I haven't thought through / tested how DataTemplateSelector would be used with this; anyone is welcome to add that to this answer.
using System.Collections;
using Xamarin.Forms;
namespace YourNamespace
{
// From https://forums.xamarin.com/discussion/19874/listview-inside-stacklayout-a-height-problem/p2, #maxx313.
public class TemplatedStack : StackLayout
{
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IList), typeof(TemplatedStack), propertyChanged: OnItemsSourceChanged);
public IList ItemsSource
{
get { return (IList)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
private static void OnItemsSourceChanged(BindableObject pObj, object pOldVal, object pNewVal)
{
var layout = pObj as TemplatedStack;
if (layout != null && layout.ItemTemplate != null)
{
layout.BuildLayout();
layout.ForceLayout();
}
}
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(TemplatedStack), propertyChanged: OnItemTemplateChanged);
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
private static void OnItemTemplateChanged(BindableObject pObj, object pOldVal, object pNewVal)
{
var layout = pObj as TemplatedStack;
if (layout != null && layout.ItemsSource != null)
layout.BuildLayout();
}
private void BuildLayout()
{
Children.Clear();
foreach (var item in ItemsSource)
{
var view = (View)ItemTemplate.CreateContent();
view.BindingContext = item;
Children.Add(view);
}
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
return base.OnMeasure(widthConstraint, heightConstraint);
}
}
}
In your XAML, do
<yourXmlns:TemplatedStack .../>
where yourXmlns must be an xmlns declaration at top of your XAML.
Usage of ItemsSource and ItemTemplate properties is similar to how you would bind an items collection and template to a ListView.
(The reason NOT to use a ListView here, is that ListView may interfere with touch events, and adds extra layout cost.)
Bind to this a collection containing a single item.
E.g. for this question, that item would be the specific sport being viewed.

data binding content refreshing on pivotitem page

I develop an application, and faced a problem with following part.
I have a 'textblock' that binds exact item in observable collection of items('Items') and shows the number. Each item has a property, called 'Count'.
When user taps current textblock, which is in stackpanel of longlistselector (binding Items), I need this textblock to show new number(idea is each time user taps textblock, number increases by 1).
The only method I discovered is each time Navigate to current pivot item(where textblock is situated) to force pivot item's reload data from observable collection, where updated items(and 'Count' property) are stored. But it is slow and doesn't work good.
Please, advice how to make the number appear in the textblock each time user taps on current textblock.
<phone:PivotItem Name="Documents" Header="Documents" Margin="0" >
<Grid>
<phone:LongListSelector ItemsSource="{Binding Items}" >
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Height="95" >
<TextBlock Tap="Plus_Tap" Visibility="Visible" Width="20" Height="50" Text="{Binding Count}" FontFamily="Segoe WP Light"
FontSize="35" Foreground="White" Margin="2,0,0,2"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
</phone:PivotItem>
namespace PackMan
{
public partial class CategoryList : PhoneApplicationPage
{
public CategoryList()
{
InitializeComponent();
DataContext = App.ViewModel;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
private void Plus_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
PackList selectedList = (sender as TextBlock).DataContext as PackList;
selectedList.Count += 1;
NavigationService.Navigate(new Uri("/CategoryList.xaml?Documents", UriKind.Relative));
}
}
}
How to refresh the data in pivot item after updating data through xaml UI?
here a guy in the very end of his posted answer to his own question wrote: "If you are using the observableCollection than no need to refresh the page. It is the magic of ViewModel." But I load data from 'Items' observable collection, which is not in ViewModel, but declared in Packlist.cs. Maybe this will help.
Here I do increase Count property of the item in ObservableCollection "Items" :
private void Plus_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
PackList selectedList = (sender as TextBlock).DataContext as PackList;
selectedList.Count += 1;
I tap on textblock, and text of the textblock shows no changes. I tap return - which brings me back to menu page, than again I open pivot item page, and see the textblock's text(number) increases by one. Change is seen only when I reload page(pivot item), since I have this in its constructor and OnNavigatedTo method:
public CategoryList()
{
InitializeComponent();
DataContext = App.ViewModel;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
LoadData() - loads OBservable collection ("Items"), which has items, and Count as one of its properties:
public void LoadData()
{
this.Items = LoadNewLists();
this.IsDataLoaded = true;
}
Maybe now you could tell me how to make text of the textblock change instantly as I tap on it(textblock). I will be thankful for any advice, since this moment has stopped me from developing my app second day now.
I figured it out. I should have read about INotifyPropertyChanged Interface and created 'Count' property using this interface's method:
public int Count
{
get
{
return _count;
}
set
{
if (value != _count)
{
_count = value;
NotifyPropertyChanged("Count");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Hope it will be helpful to anyone.

Have complex object update in view when property changed - property changed event not fired - WinRT/XAML

I have a WinRT app with a number of Users, Projects, Meetings, etc.
I have a main screen, with a main screen view model, which should display CurrentUser and has a ListView bound to CurrentUser.ProjectList.
I initialise CurrentUser in the ViewModel using a UserProvider class that gets all the required information from the database.
My problem then becomes very similar to this: Subscribe to INotifyPropertyChanged for nested (child) objects
I have a user and project model:
public class User
{
public int id { get; set; }
public string ForeName { get; set; }
public string Surname { get; set; }
... etc ...
public ObservableCollection<Project> ProjectList { get; set; }
public ObservableCollection<User> FriendList { get; set; }
... constructor
}
public class Project
{
public String Name { get; set; }
public int Id { get; set; }
public List<User> Users { get; set; }
public List<Meeting> Meetings { get; set; }
.. constructor ...
}
A view model with the following:
class HomeScreenViewModel : INotifyPropertyChanged {
private User _currentUser;
public User CurrentUser
{
get { return this._currentUser; }
set
{
if (Equals(_currentUser, value)) return;
this._currentUser = value;
RaisePropertyChanged("CurrentUser");
}
}
//[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
... I have a method in this view model that gets the current user
public async Task<bool> GetLoggedInUserAsync()
{
int testId = 0;
CurrentUser = await userProvider.GetCurrentUser(testId);
UserProjects = await userProvider.GetUsersProject(CurrentUser);
CurrentUser.ProjectList = UserProjects;
return true;
}
That is called in the view's loadState
public MainPage()
{
this.InitializeComponent();
addMeeting = new AddMeetingFlyout();
_vm = new HomeScreenViewModel();
this.DataContext = _vm;
}
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
await _vm.GetLoggedInUserAsync()
}
And my bindings in the XAML, for ProjectList and ForeName, for example, are as follows:
<CollectionViewSource
x:Name="projectsViewSource"
Source="{Binding CurrentUser.ProjectList}"/>
...
<ListView
x:Name="projectList"
ItemsSource="{Binding Source={StaticResource projectsViewSource}}"
Grid.Row="1"
SelectionMode="None"
Style="{StaticResource DraggableListView}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
IsItemClickEnabled="True"
>
<ListView.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource ProjectTileButton}" Content="{Binding Name}" Click="ProjectItem_Click" />
</DataTemplate>
</ListView.ItemTemplate>
<AddDeleteThemeTransition/>
</ListView>
...
<Button ...>
<TextBlock ...">
<Run Text="{Binding CurrentUser.ForeName}" />
</TextBlock>
</Button>
The button content, CurrentUser.ForeName fires an INotifyPropertyChanged event when CurrentUser is first initialised in the viewmodel. This is reflected in the view - but any further changes to CurrentUser.ForeName do not fire any subsequent INotifyPropertyChanged events. The ProjectList is also not displayed in the view and does not fire an INotifyPropertyChanged event even though I know it is there.
I have spent many days looking at implementing INotifyPropertyChanged so that changes to nested child complex objects (such as CurrentUser.ProjectList) will propagate up to the view. At the minute, the only way this happens is if I force a call to
this._currentUser = value;
RaisePropertyChanged("CurrentUser");
which I am testing with a button that calls a method called MakeChange() in the viewmodel
public void MakeChange()
{
User updatedCurrentUser = CurrentUser;
CurrentUser = updatedCurrentUser;
}
This works, so I know for a fact all the data is coming correctly from the database and all is as it should be - one less thing to worry about!
However, I simply cannot get the view to display user projects on page load, or when new projects are added.
I tried implementing this solution: https://gist.github.com/thojaw/705450, however, the WinRT reflection capabilites have changed and I am not sure how to get the following liens to work within the context of my project, as this is beyond me:
//from property
//in _type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
//where _inotifyType.IsAssignableFrom(property.PropertyType)
//select property;
Any help would be greatly appreciated - I honestly thought all I had to do was bind CurrentUser.ProjectList to a ListView.
As you are replacing the entire ObservableCollection itself, then you will also need to introduce another property changed event and backing field for the collection property.
There is a good example of this here

ComboBox SelectedValue doesn't show

I have a strange problem in my WinRT/C# XAML Metro app, using the Windows 8 Release Preview (latest patches installed). I'm using a ComboBox, whose values ItemsSource and SelectedValue are bound to properties in a ViewModel:
<ComboBox SelectedValue="{Binding MySelectedValue, Mode=TwoWay}"
ItemsSource="{Binding MyItemsSource, Mode=OneWay}"
Width="200" Height="30" />
Code behind:
public MainPage()
{
this.InitializeComponent();
DataContext = new TestViewModel();
}
And a very simple definition of the TestViewModel, using strings:
public class TestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<string> _myItemsSource = new List<string>
{
"Test Item 1",
"Test Item 2",
"Test Item 3"
};
public IEnumerable<string> MyItemsSource
{
get { return _myItemsSource; }
}
private string _mySelectedValue = "Test Item 2";
public string MySelectedValue
{
get { return _mySelectedValue; }
set
{
_mySelectedValue = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("MySelectedValue"));
}
}
}
}
Now I thought this simple solution should just work... But when I start the app, the SelectedValue="Test Item 2" doesn't show up, the ComboBox is left empty. By setting breakpoints I noticed that the bound values MyItemsSource and MySelectedValue are corectly retrieved from the View Model when I set the DataContext of the view. After this action, the ComboBox.SelectedValue property is actually set to "Test Item 2", but it just doesn't show! Also I noticed that when I change the selected value in the ComboBox by user action on the UI, the changed value shows up in the ComboBox and the View Model property is updated accordingly. So everything seems to work fine except the initial visualization of the MySelectedValue View Model property. I'm becoming really desperate about that...
Now while this is the simplest example, in the origin I wanted to bind whole entities to ComboBox, setting DisplayMemberPath and SelectedValuePath. Unfortunately, the same problem occurs.
I found the problem in my example: In the XAML markup I've defined the SelectedValue property before the ItemsSource property. If I swap both definitions in this way, it works:
<ComboBox ItemsSource="{Binding MyItemsSource, Mode=OneWay}"
SelectedValue="{Binding MySelectedValue, Mode=TwoWay}"
Width="200" Height="30" />
This is really odd and annoying. Now I would like to know: is this a bug or by design? I think this is a bug, because the control should be working regardless of the order of the defined properties in XAML.
this is working solution : you can find here https://skydrive.live.com/?cid=b55690d11b67401d&resid=B55690D11B67401D!209&id=B55690D11B67401D!209
<ComboBox Width="300" Height="32" HorizontalAlignment="Left" DisplayMemberPath="Name"
VerticalAlignment="Top" ItemsSource="{Binding PersonCollection}"
SelectedItem="{Binding SelectedPerson, Mode=TwoWay}"></ComboBox>
ViewModle class is
public class ViewModel:BaseViewModel
{
private Person selectedPerson;
public Person SelectedPerson {
get { return this.selectedPerson; }
set { this.selectedPerson = value;
this.RaisePropertyChanged("SelectedPerson");
}
}
public ObservableCollection<Person> PersonCollection { get; set; }
public ViewModel()
{
this.PersonCollection = new ObservableCollection<Person>();
this.PopulateCollection();
//setting first item as default one
this.SelectedPerson = this.PersonCollection.FirstOrDefault();
}
private void PopulateCollection()
{
this.PersonCollection.Add(new Person { Name="Oscar", Email="oscar#sl.net" });
this.PersonCollection.Add(new Person { Name = "Jay", Email = "jay#sl.net" });
this.PersonCollection.Add(new Person { Name = "Viral", Email = "viral#sl.net" });
}
}