I've created a custom usercontrol in my application and I try to bind to its property (BindableProperty) to ViewModel but it doesn't work for me. Am I doing something wrong?
This is the usercontrol. It is just custom stepper for the purpose of test project: "decrease" and "increase" buttons and quantity label between them.
Please notice that binding inside the usercontrol works perfect (for example Commands bindings) . What I am trying to do is bind QuantityProperty to ViewModel
namespace UsercontrolBindingTest.Usercontrols
{
using System.Windows.Input;
using Xamarin.Forms;
public class CustomQuantityStepper : ContentView
{
public static readonly BindableProperty QuantityProperty =
BindableProperty.Create(nameof(Quantity), typeof(int), typeof(CustomQuantityStepper), 0, BindingMode.TwoWay);
public int Quantity
{
get
{
return (int)base.GetValue(QuantityProperty);
}
set
{
base.SetValue(QuantityProperty, value);
this.OnPropertyChanged(nameof(this.Quantity));
}
}
public ICommand DecreaseQuantityCommand { get; private set; }
public ICommand IncreaseQuantityCommand { get; private set; }
public CustomQuantityStepper()
{
this.BindingContext = this;
this.DecreaseQuantityCommand = new Command(() => this.Quantity--);
this.IncreaseQuantityCommand = new Command(() => this.Quantity++);
this.DrawControl();
}
private void DrawControl()
{
var quantityEntry = new Entry();
quantityEntry.SetBinding(Entry.TextProperty, new Binding("Quantity", BindingMode.TwoWay));
quantityEntry.WidthRequest = 50;
quantityEntry.HorizontalTextAlignment = TextAlignment.Center;
var increaseQuantityButton = new Button { Text = "+" };
increaseQuantityButton.SetBinding(Button.CommandProperty, "IncreaseQuantityCommand");
var decreaseQuantityButton = new Button { Text = "-" };
decreaseQuantityButton.SetBinding(Button.CommandProperty, "DecreaseQuantityCommand");
var ui = new StackLayout()
{
Orientation = StackOrientation.Horizontal,
Children =
{
decreaseQuantityButton,
quantityEntry,
increaseQuantityButton
}
};
this.Content = ui;
}
}
}
The view with proof that binding between View and VM is working:
namespace UsercontrolBindingTest
{
using Usercontrols;
using Xamarin.Forms;
public class App : Application
{
public App()
{
MainPage = new ContentPage
{
BindingContext = new MainPageVM(),
Content = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
Children =
{
this.GetLabel("Title"),
this.GetCustomStepper(),
this.GetLabel("SelectedQuantity")
}
}
};
}
private Label GetLabel(string boundedPropertyName)
{
var ret = new Label();
ret.HorizontalOptions = LayoutOptions.CenterAndExpand;
ret.SetBinding(Label.TextProperty, new Binding(boundedPropertyName));
return ret;
}
private CustomQuantityStepper GetCustomStepper()
{
var ret = new CustomQuantityStepper();
var dataContext = this.BindingContext as MainPageVM;
ret.SetBinding(CustomQuantityStepper.QuantityProperty, new Binding("SelectedQuantity", BindingMode.TwoWay));
return ret;
}
}
}
And my simple ViewModel:
namespace UsercontrolBindingTest
{
using System.ComponentModel;
internal class MainPageVM : INotifyPropertyChanged
{
private int _selectedQuantity;
public int SelectedQuantity
{
get
{
return this._selectedQuantity;
}
set
{
this._selectedQuantity = value;
this.NotifyPropertyChanged(nameof(this.SelectedQuantity));
}
}
public string Title { get; set; } = "ViewModel is bound";
public MainPageVM()
{
this.SelectedQuantity = 0;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
}
I've checked plenty of different topis and blog posts but I wasn't able to find solutions for my issue. Hope for help here...
Attached sample project here: https://1drv.ms/u/s!Apu16I9kXJFtl28YBjkfwDztT9j0
You are setting the BindingContext of CustomQuantityStepper to itself (this). That's why the binding engine is looking for a public property named "SelectedQuantity" in your custom control and not in the ViewModel.
Don't set the BindingContext in the control and instead let it use the context that is currently defined and (hopefully) points to the VM.
Set the Source property of the Binding and let it point to the correct source (which would be the VM).
Related
Can I add WPF items programmatically without any manual interference with XAML? Please give me a hand.
learning xaml markup is necessary step in wpf development, but yes, wpf app can be written purely in c#:
public class MainWindow : Window
{
DataGrid myDataGrid;
public MainWindow()
{
InitializeComponent();
var root = new Grid();
myDataGrid = new DataGrid();
var items = new ObservableCollection<DgItem> { new DgItem { Name = "A" } };
myDataGrid.ItemsSource = items;
root.Children.Add(myDataGrid);
this.Content = root;
}
}
public class DgItem
{
public string Name { get; set; }
}
I am struggling in getting my head around how to model bind nested views within xamarin and MVVM.
The idea is to make a custom control. This will then be on a view. This will be in a page.
I have created a basic project with some buttons to try and get it working. The page has a dynamic grid. So in the code behind it loops through and generates the view. The page also has a "SelectedView" property. When a button is clicked I want to be able to update this property so I could update some information on the page.
Here is my code to help give an idea to what I am doing (I maybe thinking about this wrong).
Page
<ContentPage.Content>
<Grid x:Name="grid">
</Grid>
</ContentPage.Content>
Code behind
LabelPageViewModel VM { get; set; }
public LabelPage()
{
InitializeComponent();
VM = new LabelPageViewModel();
VM.InitiliseGridContent();
BindingContext = VM;
grid.RowDefinitions = new RowDefinitionCollection();
for(int i = 0; i < 5; i++)
{
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Star });
}
grid.ColumnDefinitions = new ColumnDefinitionCollection();
for (int i = 0; i < 5; i++)
{
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Star });
}
int listValue = 0;
for(int i = 0;i <4; i++)
{
for (int j = 0; j < 4; j++)
{
grid.Children.Add(VM.GridViewList[listValue],j,i);
listValue = listValue + 1;
}
}
}
ViewModel
public ObservableCollection<CustomButtonGridView> GridViewList { get; set; }
public CustomButtonGridView selectedView;
public CustomButtonGridView SelectedView
{
get { return selectedView; }
set
{
if(selectedView != value)
{
selectedView = value;
OnPropertyChanged();
}
}
}
Color backgroundColor;
public Color BackgroundColor
{
get
{
return backgroundColor;
}
set
{
if(backgroundColor!= value)
{
backgroundColor = value;
OnPropertyChanged();
}
}
}
public LabelPageViewModel()
{
}
public void InitiliseGridContent()
{
GridViewList = new ObservableCollection<CustomButtonGridView>();
//Loop through and create blank items.
for (int i = 0; i < 16; i++)
{
GridViewList.Add(new CustomButtonGridView(this));
}
SelectedView = GridViewList[0];
}
public ICommand BtnCmd
{
get { return new Command<CustomButtonGridView>(async (x) => await ButtonClicked(x)); }
}
async Task ButtonClicked(CustomButtonGridView model)
{
SelectedView = model;
foreach(var item in GridViewList)
{
if(item != SelectedView)
{
BackgroundColor = Color.Red;
}
else
{
BackgroundColor = Color.Blue;
}
}
}
View
<Grid x:Name="grid">
<buttonView:CustomButtonView x:Name="myButton"
Command="{Binding BtnCmd}"
CommandParameter="{x:Reference myContent}" Text="Button"
BackgroundColor="{Binding BackgroundColor}"></buttonView:CustomButtonView>
</Grid>
Code Behind
ButtonGridViewModel VM { get; set; }
LabelPageViewModel labelVM { get; set; }
public CustomButtonGridView(LabelPageViewModel vm)
{
InitializeComponent();
labelVM = vm;
BindingContext = labelVM;
grid.RowDefinitions = new RowDefinitionCollection();
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Star });
}
public static readonly BindableProperty SelectedCommandProperty =
BindableProperty.Create(nameof(SelectCmd), typeof(ICommand), typeof(CustomButtonGridView), null);
public ICommand SelectCmd
{
get => (ICommand)GetValue(SelectedCommandProperty);
set
{
SetValue(SelectedCommandProperty, value);
labelVM.selectedView = this;
}
}
public static void Execute(ICommand command)
{
if (command == null) return;
if (command.CanExecute(null))
{
command.Execute(null);
}
}
// this is the command that gets bound by the control in the view
// (ie. a Button, TapRecognizer, or MR.Gestures)
public Command OnTap => new Command(() => Execute(SelectCmd));
ViewModel
public ICommand BtnCmd
{
get { return new Command(async () => await ButtonClicked()); }
}
async Task ButtonClicked()
{
Application.Current.MainPage.DisplayAlert("Clicked", "Clicked", "OK");
}
public Color selectedColor;
public Color SelectedColor
{
get { return selectedColor; }
set
{
if(selectedColor != value)
{
selectedColor = value;
OnPropertyChanged();
}
}
}
Control
public class CustomButtonView : Button
{
public static readonly BindableProperty CustomTextProperty = BindableProperty.Create("CustomText", typeof(string), typeof(CustomButtonView), "Default");
public string CustomText
{
get { return (string)base.GetValue(CustomTextProperty); }
set
{
base.SetValue(CustomTextProperty, value);
Text = value;
}
}
public CustomButtonView()
{
Text = CustomText;
}
}
On my page I have a list of the Views. I then loop through and create 16 items on a 4X4 grid.
I set the first item to be the selected item.
On my View I have my custom button where I could add it's property bindings etc.
I then have a click event in the view that fires an alert. I want to change the color of the clicked button as a proof of concept. Currently all buttons turn red as none of them are marked as the "selectedItem".
What I am trying to achieve
Button is clicked on control -> notifies view -> notifies Page
The button can do an action. The view could do some logic. The page could do some logic based on button pressed.
I am not sure how to glue together multiple views and view models to get them to bind correctly. Maybe some kind of 2 way binding I am missing?
I might be thinking about this wrong and there might be a better way to achieve what I want.
Any help would be appreciated.
Many thanks
According to your description, you can try CollectionView which is easy to achieve this funciton.
CollectionView is a view for presenting lists of data using different layout specifications. It aims to provide a more flexible, and performant alternative to ListView.
CollectionView should be used for presenting lists of data that require scrolling or selection. A bindable layout can be used when the data to be displayed doesn't require scrolling or selection.
The result is like this:
For more , check: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/collectionview/introduction .
I Have problem in my navigation drawer, so after my drawer menu is clicked, its go to another page but when im press back button then its make my application closed, what i want here is when im press the back button its go back to dashboard instead of close the application here is my code in navigation drawer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using KGVC.Views.UsersInfo;
using System.Threading.Tasks;
using KGVC.Models;
using KGVC.Views.RssFeed;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace KGVC.Views.MainPages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainPage : MasterDetailPage
{
public List<MasterPageItem> menuList { get; set; }
public MainPage()
{
InitializeComponent();
menuList = new List<MasterPageItem>();
// Creating our pages for menu navigation
// Here you can define title for item,
// icon on the left side, and page that you want to open after selection
var page0 = new MasterPageItem() { Title = "Dashboard", Icon = "icon_home.png", TargetType = typeof(MainPage) };
var page1 = new MasterPageItem() { Title = "Users Profile", Icon = "icon_home.png", TargetType = typeof(BioUsers) };
var page2 = new MasterPageItem() { Title = "Points", Icon = "icon_point.png", TargetType = typeof(RssFeedView) };
var page3 = new MasterPageItem() { Title = "Inbox", Icon = "icon_inbox.png", TargetType = typeof(TestPage1) };
var page4 = new MasterPageItem() { Title = "Card Community", Icon = "icon_community.png", TargetType = typeof(TestPage1) };
var page5 = new MasterPageItem() { Title = "Offers & Promotion", Icon = "icon_point.png", TargetType = typeof(Logout) };
var page6 = new MasterPageItem() { Title = "Info & Service", Icon = "icon_info", TargetType = typeof(Logout) };
// Adding menu items to menuList
menuList.Add(page0);
menuList.Add(page1);
menuList.Add(page2);
menuList.Add(page3);
menuList.Add(page4);
menuList.Add(page5);
menuList.Add(page6);
// Setting our list to be ItemSource for ListView in MainPage.xaml
navigationDrawerList.ItemsSource = menuList;
// Initial navigation, this can be used for our home page
Detail = new NavigationPage((Page)Activator.CreateInstance(typeof(GridMenu)));
}
private void OnMenuItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = (MasterPageItem)e.SelectedItem;
Type page = item.TargetType;
Detail = new NavigationPage((Page)Activator.CreateInstance(page));
IsPresented = false;
}
}
}
and here is my MasterPageItem class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace KGVC.Views.MainPages
{
public class MasterPageItem
{
public string Title { get; set; }
public string Icon { get; set; }
public Type TargetType { get; set; }
public ICommand Command { get; set; }
}
}
ho to make that happen, should i change anyting or is this possible to do that ?
Override the OnBackPressed in MainActivity(Android)
public override void OnBackPressed()
{
if(App.Instance.DoBack)
{
base.OnBackPressed();
}
}
In my xamarin forms app (App.Instance (it is a singleton))
public bool DoBack
{
get
{
MasterDetailPage mainPage = App.Current.MainPage as MasterDetailPage;
if (mainPage != null)
{
bool canDoBack = mainPage.Detail.Navigation.NavigationStack.Count > 1 || mainPage.IsPresented;
// we are on a top level page and the Master menu is NOT showing
if (!canDoBack)
{
// don't exit the app just show Dashboard
//mainPage.IsPresented = true;
Type page = typeof(MainPage);
mainPage.Detail = new NavigationPage((Page)Activator.CreateInstance(page));
return false;
}
else
{
return true;
}
}
return true;
}
}
--- Edited ---
static App _instance;
public static App Instance { get { return _instance; } }
Set _instance=this; in App()
Would anyone know if its possible to bind data to a SfGauge?
If so how according to the needle pointer value,
NeedlePointer needlePointer = new NeedlePointer();
needlePointer.Value = 60;
needlePointer.Color = Color.Gray;
needlePointer.KnobColor = Color.FromHex("#2bbfb8");
needlePointer.Thickness = 5;
needlePointer.KnobRadius = 20;
needlePointer.LengthFactor = 0.8;
scale.Pointers.Add(needlePointer);
Thanks
Here is my Updated code however still showing zero on the guage.
namespace Drip
{
public partial class Guage : ContentPage
{
private const string Url = "https://thingspeak.com/channels/301726/field/1.json";
private HttpClient _client = new HttpClient();
private ObservableCollection<Feed> _data2;
SfCircularGauge circular;
NeedlePointer needlePointer;
public Guage()
{
...
needlePointer = new NeedlePointer();
needlePointer.Color = Color.Gray;
needlePointer.KnobColor = Color.FromHex("#2bbfb8");
needlePointer.Thickness = 5;
needlePointer.KnobRadius = 20;
needlePointer.LengthFactor = 0.8;
scale.Pointers.Add(needlePointer);
Content = circular;
}
protected override async void OnAppearing()
{
var content = await _client.GetStringAsync(Url);
var data = JsonConvert.DeserializeObject<RootObject>(content);
_data2 = new ObservableCollection<Feed>(data.Feeds);
this.BindingContext = _data2[0];
needlePointer.SetBinding(Pointer.ValueProperty, "Field1");
}
}
Model
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Drip
{
public class Feed : INotifyPropertyChanged
{
private decimal _field1 = 0;
public decimal Field1
{
get
{
return _field1;
}
set
{
if (_field1 != value)
{
_field1 = value;
OnPropertyChanged();
}
}
}
public DateTime Created_at { get; set; }
public int Entry_id { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyField1 = null)
{
{
var handler = PropertyChanged;
if (handler != null)
{
PropertyChangedEventArgs propertyChangedEvent
= new PropertyChangedEventArgs(propertyField1);
handler(this, propertyChangedEvent);
}
}
}
}
}
I have changed the following above to my code however the guage is still showing value zero, have i missed anything or used something wrong?
assuming you want to bind to the property "Value" of your BindingContext
needlePointer.SetBinding(NeedlePointer.ValueProperty, "Value");
in order for data binding to work, you need to set a BindingContext
this.BindingContext = _data2[0];
The value which we are getting for _data2[0] from json is 0. Please refer the following attached screenshot of the json data.
In the attached screenshot, highlighted the field1 item value of 0th index from the collection. So, if you bound those value with the needle pointer it will show only the 0 value. Can you please ensure what value you are getting from _data2[0]? If you are getting 0 then please get the field1 value from _data[1] or _data[2].
I'm trying to bind a TextBlock using INotifyPropertyChanged event. But it is not updating anything to the TextBlock. The TextBlock is blank. My goal is to update the status of items which are displayed in different rows. I need to update the TextBlock's text and color based on the status.
Could anyone tell me what is wrong with my code?
public class ItemStatus : INotifyPropertyChanged
{
string itemStatus;
Brush itemStatusColor;
public string ItemStatus
{
get { return itemStatus; }
set
{
itemStatus = value;
this.OnPropertyChanged("ItemStatus");
}
}
public Brush ItemStatusColor
{
get { return itemStatusColor; }
set
{
itemStatusColor = value;
this.OnPropertyChanged("ItemStatusColor");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
}
public class Items
{
List<ItemStatus> currentItemStatus;
public List<ItemStatus> CurrentItemStatus
{
get { return currentItemStatus; }
set { currentItemStatus = value; }
}
}
public partial class DisplayItemStatus : Page
{
....
....
public DisplayItemStatus()
{
foreach (Product product in lstProductList)
{
TextBlock tbItemStatus = new TextBlock();
....
Items objItems = new Items();
Binding bindingText = new Binding();
bindingText.Source = objItems;
bindingText.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
bindingText.Path = new PropertyPath(String.Format("ItemStatus"));
tbItemStatus.SetBinding(TextBlock.TextProperty, bindingText);
Binding bindingColor = new Binding();
bindingColor.Source = objItems;
bindingColor.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
bindingColor.Path = new PropertyPath(String.Format("ItemStatusColor"));
tbItemStatus.SetBinding(TextBlock.ForegroundProperty, bindingColor);
grdItemsList.Children.Add(tbItemStatus);
}
}
private void UpdateItems_Click(object sender, MouseButtonEventArgs e)
{
int intCount = 0;
List<Product> ProductList = new List<Product>();
List<ItemStatus> ItemList = new List<ItemStatus>();
ProductList = GetProducts();
foreach (Product product in ProductList)
{
intCount++;
UpdateStatus(intCount, ItemList);
}
}
public void UpdateStatus(int intIndex, List<ItemStatus> ItemList)
{
ItemStatus status = new ItemStatus();
status.ItemStatus = strOperationStatus;
status.ItemStatusColor = brshForegroundColor;
ItemList.Add(status);
}
}
Well, the specific problem here is that you're binding the TextBlock to an Item and not the ItemStatus. But you're also doing things the hard way, you really should do the binding details in XAML. Expose a collection of ItemStatus's from your view model, and have a ListBox or something with its ItemsSource bound to the collection. Then you'll need a DataTemplate which defines the TextBlock and the bindings to the ItemStatus.
Here's a good walkthrough for it in general