I am filling my settings in list view, however I wanted to leave it generic and just set class like this however when I switch the value it doesn't fire the command
for my switch I have used this to add the command.
It works outside of a list https://damienaicheh.github.io/xamarin/xamarin.forms/2019/05/17/how-to-add-command-to-a-switch-in-xamarin-forms-en.html
public class Settings
{
public string Type { get; set; }
public bool Value { get; set; }
public Command command { get; set; }
}
then I am loading specific settings like this
public CustomSettingsViewModel()
{
LoadSetting();
}
private void LoadSetting()
{
foreach (var setting in _customSettings.ScanSettings)
{
var detail = new Settings()
{
Type = setting.Type,
Value = setting.Value,
command = new Command<Settings>((value) => ExecutePageCommand(value))
};
Setting.Add(detail);
}
}
command
private void ExecutePageCommand(Settings settings)
{
foreach (var result in _customSettings.ScanSettings.Where(d => d.Type == settings.Type))
{
var detail = new ScanSetting()
{
Value = settings.Value,
};
}
}
<Grid BackgroundColor="{DynamicResource PageBackgroundColor}" x:Name="grid" HorizontalOptions="FillAndExpand" ColumnSpacing="0">
<!--Data-->
<Label Grid.Row="0" Text ="{Binding Type}" Style="{StaticResource SubLabelStyle}" HorizontalOptions="Start" Padding="10" VerticalOptions="End" />
<Switch Grid.Row="0" IsToggled="{Binding Value}" HorizontalOptions="End" VerticalOptions="CenterAndExpand" >
<Switch.Behaviors>
<control:SwitchBehaviour Command="{Binding command}" />
</Switch.Behaviors>
</Switch>
</Grid>
However I am unsure how to change the value of the switch on the exact property so it would change only one value. Can you please advise?
Related
I am in a Xamarin app I which I have to make a to-do list. I am using listview. I have to set a label text dynamically every time I add a new to-do like so: Number of tasks 2/4 where 2 are the done tasks and 4 are the total ones. Everything goes right but I found some issues when I have to update the label text. I am using the MVVM pattern. In XAML I bind the text value to SetInfoDoneText. In MainPage, I have the bindigContext set to VM(TodoListViewModel). I use INotifyPropertyChanged with OnPropertyChanged. I made a method in which the value of setInfoDoneText is changed. The problem is that the set and get is called only once and when the setInfoDoneText is updated by the method OnPropertyChanged does not fire again. Here is the code.
THE PROBLEM IS WHEN I TRY TO UPDATE lblDoneInfo text (
)
class TodoListViewModel: INotifyPropertyChanged
{
public ObservableCollection<TodoItem> todoItems { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public String setInfoDoneText;
public String SetInfoDoneText
{
get => setInfoDoneText;
set
{
setInfoDoneText = value;
OnPropertyChanged("SetInfoDoneText");
}
}
protected void OnPropertyChanged(string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
int doneTask = 0;
int totalTask = 0;
public TodoListViewModel()
{
this.todoItems = new ObservableCollection<TodoItem>();
setInfoDoneText = "Number of tasks: " + doneTask + "/" + totalTask;
}
public ICommand AddTodoCommand => new Command(AddTodoItem);
public String newTodoInputValue { get; set; }
public String selectedItem { get; set; }
public bool highPriority { get; set; }
public bool midPriority { get; set; }
public bool lowPriority { get; set; }
Color newColor = new Color();
public void AddTodoItem()
{
if (highPriority)
{
newColor = Color.Red;
AddNewItem(newColor);
highPriority = false;
}
if (midPriority)
{
newColor = Color.Orange;
AddNewItem(newColor);
midPriority = false;
}
if (lowPriority)
{
newColor = Color.Yellow;
AddNewItem(newColor);
lowPriority = false;
}
}
public TodoItem AddNewItem(Color newColor)
{
TodoItem newItem = new TodoItem(newTodoInputValue,
false,
highPriority,
midPriority,
lowPriority,
newColor);
todoItems.Add(newItem);
UpdateDoneInfo();
return newItem;
}
public ICommand RemoveTodoCommand => new Command(RemoveTodoItem);
public void RemoveTodoItem(object o)
{
TodoItem todoItemBeingRemoved = o as TodoItem;
todoItems.Remove(todoItemBeingRemoved);
}
public ICommand EditTodoCommand => new Command(EditTodoItem);
public void EditTodoItem(object o)
{
TodoItem todoItemBeingEdited = o as TodoItem;
int newIndex = todoItems.IndexOf(todoItemBeingEdited);
todoItems.Remove(todoItemBeingEdited);
TodoItem updatedTodo = AddNewItem(newColor);
//todoItems.Add(updatedTodo);
int oldIndex = todoItems.IndexOf(updatedTodo);
todoItems.Move(oldIndex, newIndex);
}
public String UpdateDoneInfo()
{
totalTask = todoItems.Count;
foreach (TodoItem item in todoItems)
{
if (item.complete) doneTask++;
}
return setInfoDoneText = "Number of tasks: " + doneTask + "/" + totalTask;
}
}
<ContentPage.BindingContext>
<local:TodoListViewModel/>
</ContentPage.BindingContext>
<StackLayout>
<Entry
x:Name="inputField"
Text="{Binding newTodoInputValue}"
Placeholder="Enter a todo..."
/>
<Label x:Name="lblDoneInfo" Text="{Binding SetInfoDoneText, Mode=TwoWay }">
</Label>
<FlexLayout AlignItems="Center" JustifyContent="SpaceBetween">
<input:CheckBox x:Name="highP"
IsChecked="{Binding highPriority}"
CheckChangedCommand="{Binding AddTodoCommand}"
Margin="0,0,20,0" />
<Label Text="High Priority" FontSize="Medium"/>
<input:CheckBox x:Name="midP"
IsChecked="{Binding midPriority}"
CheckChangedCommand="{Binding AddTodoCommand}"
Margin="0,0,20,0" />
<Label Text="Medium Priority" FontSize="Medium"/>
<input:CheckBox x:Name="lowP"
IsChecked="{Binding lowPriority}"
CheckChangedCommand="{Binding AddTodoCommand}"
Margin="0,0,20,0" />
<Label Text="Low Priority" FontSize="Medium"/>
</FlexLayout>
<ListView x:Name="todoList" ItemsSource="{Binding todoItems}" SelectedItem="{Binding selectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell Height="20">
<FlexLayout JustifyContent="SpaceBetween" AlignItems="Center" Padding="20,0">
<ContentView>
<FlexLayout AlignItems="Center">
<input:CheckBox IsChecked="{Binding complete}" Margin="5" />
<Label x:Name="todoText" TextColor="{Binding color}" Text="{Binding todoText}" FontSize="Large"/>
</FlexLayout>
</ContentView>
<ImageButton
Source="editar_24.png"
BackgroundColor="Transparent"
WidthRequest="100"
HeightRequest="100"
Margin="0,0,20,0"
Command="{Binding Path=BindingContext.EditTodoCommand,
Source={x:Reference todoList}}"
CommandParameter="{Binding .}"/>
<ImageButton
Source="basura_24.png"
BackgroundColor="Transparent"
WidthRequest="100"
HeightRequest="100"
Command="{Binding Path=BindingContext.RemoveTodoCommand,
Source={x:Reference todoList}}"
CommandParameter="{Binding .}"/>
</FlexLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
PropertyChanged only fires when you set the value of the public property SetInfoDoneText. Your code is setting the value of the field setInfoDoneText (lowercase).
It is generally best practice to make the field private to prevent this
private String setInfoDoneText;
I have a collection view with a checkbox.
It looks like the following:
[Image][PetName][Checkbox]
I want to create a string with all the names of the pets which have been selected the pass this value through a function.
I have tried the following code but I am getting object reference is null in selectedPets.Add(ob) I'm sure Im proably going the wrong way with this but I am new to coding.
public List<PetProfile> selectedPets;
private void CheckBox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var checkbox = sender as CheckBox;
var ob = checkbox.BindingContext as PetProfile;
if (ob != null)
{
selectedPets.Add(ob);
}
}
private string CreatePetName()
{
var stringBuilder = new StringBuilder();
var listlenght = selectedPets.Count;
foreach (var pet in selectedPets)
{
if (selectedPets.Count == 0)
{
stringBuilder.Append(pet.PetName);
}
else if (listlenght > 0 && pet == selectedPets[0] )
{
stringBuilder.Append(pet.PetName + " ");
}
else if (pet == selectedPets[listlenght])
{
stringBuilder.Append(" " + pet.PetName );
}
else
{
stringBuilder.Append(" " + pet.PetName + " ");
}
}
return stringBuilder.ToString();
}
private async void SubmitBtn_Clicked(object sender, EventArgs e)
{
var Petnames = CreatePetName();
}
XAML:
<CollectionView x:Name="petCollectionView" ItemsSource="{Binding EmptyPetInfo}" HeightRequest="200">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10" RowDefinitions="80" ColumnDefinitions="120,60,60">
<Image Grid.Column="0"
Grid.Row="0"
x:Name="PetImage"
Source="{Binding imageUrl}"/>
<Label Grid.Column="1"
Grid.Row="0"
Text="{Binding PetName}"
FontAttributes="Bold"
x:Name="labelpetname" VerticalTextAlignment="Center" HorizontalTextAlignment="Center"/>
<CheckBox Grid.Row="0" Grid.Column="2" HorizontalOptions="End" IsChecked="{Binding Selected, Mode=TwoWay}" BindingContext="{Binding .}" CheckedChanged="CheckBox_CheckedChanged"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
I want to create a string with all the names of the pets which have been selected the pass this value through a function.
I suggest you don't need to use CheckBox_CheckedChanged event to get selected PetName, you can create one class name Petclass, implementing INotifyPropertyChanged, to notify Selected property changed.
public class Petclass:ViewModelBase
{
public string imageUrl { get; set; }
public string PetName { get; set; }
private bool _Selected;
public bool Selected
{
get { return _Selected; }
set
{
_Selected = value;
RaisePropertyChanged("Selected");
}
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then loading some data to test in collectionview. foreach EmptyPetInfo Collectionview ItemsSource="{Binding EmptyPetInfo}" to check Selected property is true or false.
<CollectionView
x:Name="petCollectionView"
HeightRequest="200"
ItemsSource="{Binding EmptyPetInfo}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid
Padding="10"
ColumnDefinitions="120,60,60"
RowDefinitions="80">
<Image
x:Name="PetImage"
Grid.Row="0"
Grid.Column="0"
Source="{Binding imageUrl}" />
<Label
x:Name="labelpetname"
Grid.Row="0"
Grid.Column="1"
FontAttributes="Bold"
HorizontalTextAlignment="Center"
Text="{Binding PetName}"
VerticalTextAlignment="Center" />
<CheckBox
Grid.Row="0"
Grid.Column="2"
HorizontalOptions="End"
IsChecked="{Binding Selected, Mode=TwoWay}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
public partial class Page17 : ContentPage
{
public ObservableCollection<Petclass> EmptyPetInfo { get; set; }
public Page17()
{
InitializeComponent();
EmptyPetInfo = new ObservableCollection<Petclass>()
{
new Petclass(){imageUrl="check.png",PetName="pet 1"},
new Petclass(){imageUrl="delete.png",PetName="pet 2"},
new Petclass(){imageUrl="favorite.png",PetName="pet 3"},
new Petclass(){imageUrl="flag.png",PetName="pet 4"}
};
this.BindingContext = this;
}
private void btn1_Clicked(object sender, EventArgs e)
{
var stringBuilder = new StringBuilder();
foreach (Petclass pet in EmptyPetInfo)
{
if(pet.Selected)
{
stringBuilder.Append(pet.PetName + " ");
}
}
string str = stringBuilder.ToString();
}
}
Using ObservableCollection Class, represent a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
you need to initialize selectedPets before you use it
public List<PetProfile> selectedPets = new List<PetProfile>();
i´ve got 2 things: second one is a list. when i´m clicking the "+1" button, a new element is createt with the name "list.Count"(so first one is 1, 2, 3, 4...). this is working very good. but for the first thing i wanna have only the vurrent value of the "Text"(from the current newest object in the list). But: i do not only want a counter. Maybe you can unterstand better if you check out my code:
<StackLayout>
<StackLayout>
<StackLayout Orientation="Horizontal" >
<Label Text="Erg:" HorizontalOptions="StartAndExpand" VerticalOptions="StartAndExpand" />
<Label Text="{Binding Path=Nummer, Source=Num}" HorizontalOptions="EndAndExpand" VerticalOptions="StartAndExpand" /> *
</StackLayout>
<Button Text="+1" Command="{Binding CountUpCommand}" /> **
<Button Text="Erg x Erg" />
</StackLayout>
<Label Text="--------------" />
<StackLayout>
<ListView ItemsSource="{Binding Nummer}"> **
<ListView.ItemTemplate>
<DataTemplate>
<TextCell
Text="{Binding Num}" **
/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
so my question is at the Line with only one * at the end. the other 3 lines with two ** are working. what do i have to do to get the right Binding-Path or -Statment or something like this, to get the number shown up at labelsText ? i think there´s something missing like "itemsource"..
so here s the code from Itemsource (from My CounterVM):
public ObservableCollection<NumberViewModel> Nummer { get; private set; } = new ObservableCollection<NumberViewModel>();
public ICommand CountUpCommand { get; private set; }
public CounterViewModel()
{
CountUpCommand = new Command(CountUp);
}
public void CountUp()
{
int newNumber = Nummer.Count + 1;
Nummer.Add(new NumberViewModel { Num = newNumber});
}
and here the NumverVM, same code as in number.cs:
public class NumberViewModel : BaseViewModel
{
public int Num { get; set; }
}
You can add a Property inside your ViewModel to get the correct value:
public class CounterViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public string LastItemsText => Nummer.Last()?.Num.ToString();
public ObservableCollection<NumberViewModel> Nummer { get; private set; } = new ObservableCollection<NumberViewModel>();
public ICommand CountUpCommand { get; private set; }
public CounterViewModel()
{
CountUpCommand = new Command(CountUp);
}
public void CountUp()
{
int newNumber = Nummer.Count + 1;
Nummer.Add(new NumberViewModel { Num = newNumber});
PropertyChanged(this, new PropertyChangedEventArgs(nameof(LastItemsText));
}
}
And instead of
<Label Text="{Binding Path=Nummer, Source=Num}"
do this:
<Label Text="{Binding LastItemsText}"
I have masterdetailpage that have Logout function inside that (so instead navigating to other page it will shows display alert), but i dont know how to insert the logout method inside the masterdetailpage, i already tried using ICommand but seem it didnt works and make my application force close .
Here is my MasterPageItem Model
public class MasterPageItem
{
public string Title { get; set; }
public string Icon { get; set; }
public Type TargetType { get; set; }
public ICommand Commando { get; set; }
}
here is the listview for MasterDetailPage
<ListView x:Name="navigationDrawerList"
RowHeight="45"
SeparatorVisibility="None"
BackgroundColor="#000000"
ItemSelected="OnMenuItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<!-- Main design for our menu items -->
<StackLayout BackgroundColor="#000000" VerticalOptions="FillAndExpand"
Orientation="Horizontal"
Padding="20,10,0,10"
Spacing="20">
<Image Source="{Binding Icon}"
WidthRequest="60"
HeightRequest="60"
VerticalOptions="Center" />
<Label FontFamily="Panton-LightCaps.otf#Panton-LightCaps" Text="{Binding Title}"
FontSize="Medium"
VerticalOptions="Center"
TextColor="White"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
and i tried to insert the method like this
public ICommand GetOff { get; private set; }
public MainPage()
{
GetOff = new Command(LogoutCommand)
var page9 = new MasterPageItem() { Title = "LOGOUT", Commando = GetOff };
}
public async void LogoutCommand ()
{
var result = await this.DisplayAlert("Alert!", "Do you really want to exit?", "Yes", "No");
if (result == true)
{
App.AuthenticationClient.UserTokenCache.Clear(Constants.ApplicationID);
Application.Current.MainPage = new NavigationPage(new NewPageLogin());
}
}
Is there another way to insert method inside MasterdetailPage ? Any suggestion would be appreciated
Option 1
You can add a footer to your listview and attach a tap gesture recognizer to it, like this:
<ListView.Footer>
<StackLayout BackgroundColor="#000000"
VerticalOptions="FillAndExpand"
Orientation="Horizontal"
Padding="20,10,0,10"
Spacing="20">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding LogoutCommand}" />
</StackLayout.GestureRecognizers>
<Image Source="YourIcon"
WidthRequest="60"
HeightRequest="60"
VerticalOptions="Center" />
<Label FontFamily="Panton-LightCaps.otf#Panton-LightCaps"
Text="{Binding Title}"
FontSize="Medium"
VerticalOptions="Center"
TextColor="White"/>
</StackLayout>
</ListView.Footer>
It should go inside the ListView tag.
As you see, it supports Command, so you can use the one you already have.
Option 2
You can set the TargetType of your sign out item to null and do something like this:
private void OnMenuItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as MasterPageItem;
if (item == null)
return;
// Check if sign out was tapped
if (item.TargetType != null)
{
var page = (Page)Activator.CreateInstance(item.TargetType);
page.Title = item.Title;
Detail = new NavigationPage(page);
IsPresented = false;
}
else
{
// Manage your sign out action
var result = await this.DisplayAlert("Alert!", "Do you really want to exit?", "Yes", "No");
if (result == true)
{
App.AuthenticationClient.UserTokenCache.Clear(Constants.ApplicationID);
Application.Current.MainPage = new NavigationPage(new NewPageLogin());
}
}
}
Im having a hard time trying to figure out how to set up a two-way binding for a control inside a listview.
Im using ReactiveUI and Xamarin.Forms.
In this case i would like to load a list of objects that have a quantity. This is set initially when the page loads. However i would like to be able to change these quantity values in the view when the program is run. I used an Entry for that.
Setting up a two-way Binding for the List itself (done in code behind, the reactive way) is not possible. It will error.
Is there another way to observe changes done to the Text property in the Entry control and reflect them to the according item from the list in my viewmodel?
I've been having trouble finding a solution for this and don't really know how to go about this.
Here is my XAML code:
<CustomControls:AutoLoadListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="20,0,0,0" Orientation="Vertical" HorizontalOptions="StartAndExpand">
<Label Margin="0,5,0,-5" Style="{StaticResource ViewCellPrimaryLabelStyle}" x:Name="txt" Text="{Binding itemname}" />
<Label Margin="0,-5,0,5" Style="{StaticResource ViewCellSecondaryLabelStyle}" x:Name="barcode" Text="{Binding productcode}" />
</StackLayout>
<Entry Margin="5,0,5,0" x:Name="quantity" Text="{Binding quantity}">
<Entry.BindingContext>
<ViewModel:AankoopEditViewModel />
</Entry.BindingContext>
</Entry>
<Image Margin="5,5,5,5" x:Name="delete" Source="{Mobile:ImageResource tbin_pos.png}">
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Path=BindingContext.DeleteCommand,Source={x:Reference Name=AankoopEditPage}}"
CommandParameter="{Binding}" />
</Image.GestureRecognizers>
</Image>
</ViewCell>
</DataTemplate>
</CustomControls:AutoLoadListView.ItemTemplate>
My Viewmodel:
public class AankoopEditViewModel : BaseViewModel
{
private VmPurchase Purchase;
public AankoopEditViewModel()
{
PurchaseList = new ReactiveObservableCollection<AankoopEditListItem>()
{
ChangeTrackingEnabled = true
};
this.WhenAnyValue(x => x.PurchaseID).SubscribeOn(RxApp.MainThreadScheduler).Subscribe((x) =>
{
this.Purchase = DatabaseHelper.Purchase.LoadSingleById<VmPurchase>(PurchaseID);
if (Purchase != null)
{
this.Title = Purchase.supplier.name;
using (PurchaseList.SuppressChangeNotifications())
{
foreach (var detail in Purchase.purchasedetails)
{
PurchaseList.Add(new AankoopEditListItem { productcode = detail.item.code, itemname = detail.item.namenl, identifier = detail.key, quantity = detail.quantity.ToString() });
}
}
}
});
try
{
this.WhenAnyValue(x => x.PurchaseList).SubscribeOn(RxApp.MainThreadScheduler).Subscribe((x) =>
{
Console.WriteLine("The List has changed");
});
}
catch (Exception e)
{
return;
}
}
private string _purchaseID;
public string PurchaseID
{
get { return _purchaseID; }
set { this.RaiseAndSetIfChanged(ref _purchaseID, value); }
}
private ReactiveObservableCollection<AankoopEditListItem> _purchases;
public ReactiveObservableCollection<AankoopEditListItem> PurchaseList
{
get
{
return this._purchases;
}
set
{
this.RaiseAndSetIfChanged(ref _purchases, value);
}
}
My Model :
public class AankoopEditListItem : ReactiveObject
{
public string identifier { get; set; }
public string itemname { get; set; }
public string productcode { get; set; }
public string quantity { get; set; }
}
Be careful, when you do this
<Entry.BindingContext>
<ViewModel:AankoopEditViewModel />
</Entry.BindingContext>
you create a new instance of your view model for each item and you bind your Entry to it. Just remove it and keep the binding as it is (Text="{Binding quantity}") if you want to bind your entry to the row view model