I feel this is weird. i can't get the Radio button to pre-select a saved value and it's driving me mad. I have this xaml:
<StackLayout Orientation="Horizontal" RadioButtonGroup.GroupName="Parities"
RadioButtonGroup.SelectedValue="{Binding Parity}">
<RadioButton Value="1" Content="Income" />
<RadioButton Value="-1" Content="Expense" />
<RadioButton Value="0" Content="Neutral" />
</StackLayout>
Furthermore, even if I replace SelectedValue with a hard coded literal value "1" (for Income), the radio button still show up blank. The only way that works is by setting IsChecked on each of the 3 options to have the them pre-selected.
What am I missing?
Based on your code ,I created a simple demo, but I couldn't reproduce this problem. It just works properly.
You can refer to the following code:
MyPage.xaml
<ContentPage.BindingContext>
<radiobuttondemos:MyViewModel></radiobuttondemos:MyViewModel>
</ContentPage.BindingContext>
<StackLayout>
<StackLayout Orientation="Horizontal" RadioButtonGroup.GroupName="{Binding GroupName}"
RadioButtonGroup.SelectedValue="{Binding Parity}">
<RadioButton Value="1" Content="Income" />
<RadioButton Value="-1" Content="Expense" />
<RadioButton Value="0" Content="Neutral" />
</StackLayout>
</StackLayout>
The MyViewModel.cs
public class MyViewModel : INotifyPropertyChanged
{
string groupName;
object parity;
public object Parity
{
get => parity;
set
{
parity = value;
OnPropertyChanged(nameof(Parity));
}
}
public MyViewModel () {
GroupName = "Parities";
Parity = "1";
}
public string GroupName
{
get => groupName;
set
{
groupName = value;
OnPropertyChanged(nameof(GroupName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Note:
In the constructor of MyViewModel, I initialize the value of variable Parity as follows:
Parity = "1";
And if we initialize a value as follows, the UI will not pre-select the saved value :
Parity = 1;
Related
I am facing an issue when I submit my form in xamarin form using mvvm architecture my form UI is still able and user can interact while fetching the data from server. I want to disable my UI elements when my submit button is running to fetch the data from server. Actually, I want to bind isEnabled property in my viewmodel. But I do not know how to set it to bool value from my view model and then bind it to the UI elements. What i need to add in my set function so that when someone click on submit button my UI elements will be inactive and user can not edit till the response comes from server.
what to do please assist. Here is my code.
Blockquote
<StackLayout>
<Entry x:Name="entryFullName"
Text="{Binding FullName}"
Placeholder="Full Name"
IsEnabled="{Binding block}"
/>
<Picker x:Name="pickerGender"
Title="Gender"
ItemsSource="{Binding Genders}"
SelectedItem="{Binding SelectedGender}"
IsEnabled="{Binding gender}"
/>
</StackLayout>
<StackLayout>
<Button x:Name="btnSubmit"
Command="{Binding SubmitCommand}"
Text="Submit"
/>
</StackLayout>
<ActivityIndicator IsVisible="{Binding IsBusy}" IsRunning="{Binding IsBusy}" />
here is my code for my viewmodel submit button function
Blockquote
private string _Block;
public string Block
{
get { return _Block }
set { _Block = value; OnPropertyChanged(); }
}
private void OnSubmit()
{
if (string.IsNullOrEmpty(this.FullName))
{
this.ErrorOccurred?.Invoke(this, "Please enter full name");
return;
}
Device.BeginInvokeOnMainThread(async () => await this.SaveProfile();
}
first, bind all of your IsEnabled properties to the same VM property
<Entry x:Name="entryFullName" IsEnabled="{Binding NotBusy}" ... />
<Picker x:Name="pickerGender" IsEnabled="{Binding NotBusy}" ... />
...
<Button x:Name="btnSubmit" IsEnabled="{Binding NotBusy}" ... />
then in your MV create a bool property
private bool _NotBusy = true;
public bool NotBusy
{
get { return _NotBusy }
set { _NotBusy = value; OnPropertyChanged(); }
}
finally, when saving set the property
private void OnSubmit()
{
if (string.IsNullOrEmpty(this.FullName))
{
this.ErrorOccurred?.Invoke(this, "Please enter full name");
return;
}
NotBusy = false;
Device.BeginInvokeOnMainThread(async () => await this.SaveProfile();
}
you can add a property IsNotSubmitting,
private bool _isNotSubmitting = true;
public bool IsNotSubmitting {
get => _isNotSubmitting ;
set {
_isNotSubmitting = value;
OnPropertyChanged();
}
}
binding in Xaml:
<Entry x:Name="entryFullName"
Text="{Binding FullName}"
Placeholder="Full Name"
IsEnabled="{Binding IsNotSubmitting}" />
now you can set "IsNotSubmitting=false" in the beginning of method SubmitCommand, and you can set "IsNotSubmitting=true" when the commiting is finished
<Frame HasShadow="False">
<StackLayout Orientation="Vertical" >
<Entry Placeholder="NAME" x:Name="name"></Entry>
<Entry Placeholder="SURNAME" x:Name="surname"></Entry>
<StackLayout Orientation="Horizontal" >
<Label Text="BIRTHDATE" VerticalOptions="Center" HorizontalOptions="Center" ></Label>
<DatePicker x:Name="birdthdate" HorizontalOptions="Center" VerticalOptions="Center"></DatePicker>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="PICK YEARS" HorizontalOptions="Center" VerticalOptions="Center"></Label>
<Picker Title="YEARS" x:Name="years" HorizontalOptions="Center" VerticalOptions="Center"></Picker>
</StackLayout>
<StackLayout Spacing="0">
<Label Text="Number of docs:"></Label>
<Entry Keyboard="Numeric" x:name="docs"></Entry>
</StackLayout>
<Button Text="SAVE" TextColor="White" Padding="0,-20" BackgroundColor="#07987f" IsEnabled="false" >
</Button>
</StackLayout>
</Frame>
My idea is only when user will enter Name Surname Birdthdate Years NumberOfDocs the button will become enable and can save the data. Any suggestion how to do that?
Here is the logic for a simple login with login name + password, where the login button only gets enabled when LoginName and LoginPassword contains text:
private string _loginName;
public string LoginName
{
get { return _loginName; }
set
{
SetProperty(ref _loginName, value);
RaisePropertyChanged("IsLoginButtonEnabled");
}
}
private string _loginPassword;
public string LoginPassword
{
get { return _loginPassword; }
set
{
SetProperty(ref _loginPassword, value);
RaisePropertyChanged("IsLoginButtonEnabled");
}
}
public bool IsLoginButtonEnabled
{
get
{
if (!string.IsNullOrEmpty(LoginName) &&
!string.IsNullOrEmpty(LoginPassword))
{
return true;
}
return false;
}
}
Just extend this to your needs and it should work.
There are multiple ways you can do that. The easiest way is like the answer from Dennis Schröer. But it doesn't look like you are using MVVM so i have another solution using converters.
Change your button to this:
<Button Padding="0,-20"
BackgroundColor="#07987f"
Text="SAVE"
TextColor="White">
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource EnableButtonConverter}">
<Binding Path="Text"
Source="{x:Reference name}" />
<Binding Path="Text"
Source="{x:Reference surname}" />
<Binding Path="Date"
Source="{x:Reference birdthdate}" />
<Binding Path="SelectedItem"
Source="{x:Reference years}" />
</MultiBinding>
</Button.IsEnabled>
</Button>
The property IsEnabled is bound to all the properties you want it to be dependent on.
The converter does the logic:
public class EnableButtonConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var name = (string)values[0];
var surname = (string)values[1];
//var date = (DateTime)values[2];
//var year = (string)values[3];
return !string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(surname); //&& !year.Equals("YEARS"); //Todo: add a check for the date
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Put the Converter in your pages ResourceDictionary and you are good to go.
PS: It's better for performance to use Grid-layout instead of multiple StackLayouts
I have a ListView with a data template. I am trying to have a custom component that supports binding for the content of the data template.
Here is the ListView in the page:
<ListView ItemsSource="{Binding List}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="entities:ListItem">
<ViewCell>
<components:ListItemView ListItem="{Binding}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And this is the ListItemView declaration:
public partial class ListItemView : StackLayout
{
public static readonly BindableProperty ListItemProperty
= BindableProperty.Create(
nameof(ListItem), typeof(ListItem), typeof(ListItemView), null,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: ListItemPropertyChanged);
static void ListItemPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = (ListItemView)bindable;
view.ListItem = (ListItem)newValue;
}
public ListItem ListItem
{
get => (ListItem)GetValue(ListItemProperty);
set
{
SetValue(ListItemProperty, value);
if (_viewModel != null) // never hits this break point
_viewModel.ListItem = value;
}
}
I had a breakpoint on the line with the comment. This breakpoint was never hit. ListItemView however does get initialized and created.
Update
I tried a simple demo to ensure the issue was in the binding,
<StackLayout Padding="5">
<Label Text="{Binding Demo.Title}" />
<components:CheckListView ListItem="{Binding Demo}" />
</StackLayout>
The above code was outside the list view and I am able to see the title. The breakpoint is still not hit.
I have custom content view with a Label as title and another Label as detail and an edit Icon ; when the icon is clicked detail label is converted to Entry to make changes and the changes are carried over to binding.
I have bound multiple of these custom views to different properties of same object and trying to edit each one and move to next one, the problem is it seems to duplicate the individual views
I have also put x:Name but still it duplicates same value to the views above it ..
Just the edit of Lastname
Now if I move to 3rd view and edit it , it replicates new value to all previously edited values. - for lastname in this case which is weird considering its not same view used in the page and on debug it hits the method only once.
Custom content view:
<StackLayout Orientation="Horizontal"
VerticalOptions="Start"
Padding="25,10,25,10">
<StackLayout x:Name="stackLayoutDetail"
HorizontalOptions="FillAndExpand">
<Label x:Name="title"
Text="{Binding Title}" />
<Label x:Name="detail"
Text="{Binding Detail}"
FontSize="Large"
FontAttributes="Bold" />
</StackLayout>
<Image x:Name="editIcon"
Source="edit_icon.png"
WidthRequest="25"
HeightRequest="25"
IsVisible="{Binding EditIconVisible}">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="EditIcon_Clicked" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
Code behind:
private static Entry newEntry = new Entry();
public static readonly BindableProperty DetailProperty = BindableProperty.Create(propertyName: nameof(Detail),
returnType: typeof(string),
declaringType: typeof(LabelledEntrywithIcon),
defaultValue: default(string));
public string Detail
{
get
{
return (string)GetValue(DetailProperty);
}
set => SetValue(DetailProperty, value);
}
private void EditIcon_Clicked(object sender, System.EventArgs e)
{
detailLabel = (Label)stackLayoutDetail.Children[1];
stackLayoutDetail.Children.RemoveAt(1);
newEntry.Text = Detail;
stackLayoutDetail.Children.Add(newEntry);
editIcon.IsVisible = false;
newEntry.Completed += NewEntry_Completed;
}
private void NewEntry_Completed(object sender, System.EventArgs e)
{
try
{
var _newText = newEntry.Text;
detailLabel.Text = _newText;
stackLayoutDetail.Children.RemoveAt(1);
stackLayoutDetail.Children.Add(detailLabel);
Detail = _newText;
editIcon.IsVisible = true;
}
catch (System.Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
Page
<local:LabelledEntrywithIcon x:Name="firstName"
Title="First Name"
Detail="{Binding Fella.FirstName}" />
<local:LabelledEntrywithIcon x:Name="lastname"
Title="Last Name"
Detail="{Binding Fella.LastName}" />
<local:LabelledEntrywithIcon x:Name="gender"
Title="Gender"
Detail="{Binding Fella.Gender}" />
Code behind:
ViewModel=new MainViewModel();
BindingContext = ViewModel;
Complete code to test is at Github repo : https://github.com/pmahend1/CustomViewDuplicationIssue
Strange but I changed a line of code and it works as expected now.
On the class variables changed private static Entry newEntry= new Entry(); to
private static Entry newEntry;
in EditIcon_Clicked method instead of newEntry.Text = Detail; used
newEntry = new Entry { Text = Detail };
I am not sure why it was taking same reference even though its new Entry for each LabelledEntrywithIcon
Instead of creating a new entry and finding and removing the label and adding the new entry after, you could simplify your problem by:
<StackLayout Orientation="Horizontal"
VerticalOptions="Start"
Padding="25,10,25,10">
<StackLayout x:Name="stackLayoutDetail"
HorizontalOptions="FillAndExpand">
<Label x:Name="title"
Text="{Binding Title}" />
<Label x:Name="detail"
Text="{Binding Detail}"
IsVisible="{Binding ShowLabel}"
FontSize="Large"
FontAttributes="Bold" />
<Entry ... IsVisible="{Binding ShowEntry}" ... />
</StackLayout>
<Image x:Name="editIcon"
Source="edit_icon.png"
WidthRequest="25"
HeightRequest="25"
IsVisible="{Binding ShowLabel}">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="EditIcon_Clicked" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
Note that I intentionally wrote ... inside the entry element as Placeholder for all customizations you might want do there (font size, etc...).
Now you add two BindablyProperties (type bool) ShowEntry and ShowLabel, where ShowLabel defaults to true and ShowEntry defaults to false.
Now all you have to do is to adapt your EditIcon_Clicked Event:
private void EditIcon_Clicked(object sender, System.EventArgs e)
{
ShowLabel = false;
ShowEntry = true;
newEntry.Text = Detail;
newEntry.Completed += NewEntry_Completed;
}
And adapt NewEntry_Completed to
private void NewEntry_Completed(object sender, System.EventArgs e)
{
try
{
var _newText = newEntry.Text;
detailLabel.Text = _newText;
ShowLabel = true;
ShowEntry = false;
Detail = _newText;
}
catch (System.Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
Basically this does the same as your solution, however you save yourself from having to push UI Items around in your codebehind and especially the bugs and errors coming with it.
I have a custom control CustomTextBox.xaml:
<AbsoluteLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Core.Controls.CustomTextBox"
xmlns:controls="clr-namespace:MyApp.Core.Controls;assembly=MyApp.Core"
BackgroundColor="White">
<AbsoluteLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="OnTapped"/>
</AbsoluteLayout.GestureRecognizers>
<Entry x:Name="textValueEntry" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0.5, 1, 0.9, 0.9" FontAttributes="Bold"/>
<Label x:Name="placeholderLabel" AbsoluteLayout.LayoutFlags="PositionProportional" AbsoluteLayout.LayoutBounds="0.05, 0.5" FontSize="18"/>
</AbsoluteLayout>
I want to be able to bind to the textValueEntry control from the parent view. So I added a bindable property in CustomTextBox.xaml.cs:
private string _textValue;
public string TextValue
{
get
{
return _textValue;
}
set
{
_textValue = value;
}
}
public static BindableProperty TextValueProperty = BindableProperty.Create(nameof(TextValue), typeof(string), typeof(CustomTextBox), string.Empty, BindingMode.TwoWay, null,
(bindable, oldValue, newValue) =>
{
(bindable as CustomTextBox).textValueEntry.Text = (string)newValue;
});
I try to bind to it from the parent view like this:
<controls:CustomTextBox HorizontalOptions="FillAndExpand" VerticalOptions="CenterAndExpand" HeightRequest="50" TextValue="{Binding UsernamePropertyInViewModel, Mode=TwoWay}"/>
TextValue property gets set with UsernamePropertyInViewModel when I launch the app, as I can see it in textValueEntry. But when I change the text in textValueEntry it doesn't update UsernamePropertyInViewModel. How can I bind to it so it updates UsernamePropertyInViewModel when I change the text in textValueEntry?
As far as I can tell your CustomTextBox entry textValueEntry doesn't Bind to your TextValue property.
Also your TextValue property needs to look like this for BindableProperties. You need to set your BindableProperty to the appropriate value for a Binding. No need for a private backing variable to TextValue.
public string TextValue
{
get => (string)GetValue(TextValueProperty);
set => SetValue(TextValueProperty, value);
}
Parent.Xaml
<Entry x:Name="textValueEntry" Text="{Binding Path=TextValue, Source={x:Reference Page}}" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0.5, 1, 0.9, 0.9" FontAttributes="Bold"/>
Your Page will need an x:Name="Page" for the Binding Source