I have a problem with prism.forms and propertychanged.
I have settingsmodel,settingsviewmodel and settingspage that shown code below,
SettingsModel
public class SettingsModel: BindableBase
{
public string Url { get; set; }
public bool IsEnabled{get;set;}
public string ApiUrl { get; set; }
public SettingsModel()
{
this.Url = string.Empty;
this.IsEnabled = false;
this.ApiUrl = string.Empty;
}
}
SettingsViewModel
[ImplementPropertyChanged]
public class SettingsPageViewModel : ViewModelBase
{
readonly INavigationService _navigationService;
readonly IUserDialogs _userDialogs;
readonly IProductService _productService;
#region Constructor
public SettingsPageViewModel(INavigationService navigationService,
IUserDialogs userDialogs, IProductService productService)
{
_navigationService = navigationService;
_userDialogs = userDialogs;
_productService = productService;
this.Settings = new SettingsModel();
SaveCommand = new DelegateCommand(Save).ObservesCanExecute((vm) => Settings.IsEnabled);
}
#endregion
#region Model
SettingsModel _settingsModel;
public SettingsModel Settings
{
get { return _settingsModel; }
set {
if (_settingsModel != null)
_settingsModel.PropertyChanged -= MyPersonOnPropertyChanged;
SetProperty(ref _settingsModel, value);
if (_settingsModel != null)
_settingsModel.PropertyChanged += MyPersonOnPropertyChanged;
Validation();
}
}
public bool IsLoading { get; set; }
public DelegateCommand SaveCommand { get; set; }
#endregion
void MyPersonOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
Validation();
}
#region Methods
async void Save()
{
var result = await _productService.GetProducts(Priority.UserInitiated,"","","");
}
void Validation()
{
Settings.IsEnabled = !string.IsNullOrEmpty(Settings.ApiUrl) ? true : false;
}
#endregion
}
And SettingsPage XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="XXX.Warehouse.SettingsPage">
<Entry Text="{Binding Settings.ApiUrl}" Margin="0,5,0,5"
Placeholder="www.example.com" HorizontalOptions="FillAndExpand" />
<Button Text="Save"
FontSize="16"
BorderRadius="5"
TextColor="White"
BackgroundColor ="#578A17"
IsEnabled="{Binding Settings.IsEnabled}"
Command="{Binding SaveCommand}" />
I want to do when user enter url than IsEnabled property will true, when Url is empty than IsEnabled property will false and save button if IsEnabled is false, button not enabled.
My main problem is, i write Entry url but propertychanged event not fired?
How can i solve this?
Thank you.
Related
I would like to clear entry text from my ViewModel which is binded there. In the code below I tried it by using a RelayCommand, but it doesn't work.
What i want to accomplish: When clicking button named AddQuestionToQuiz, a function is executed by using Command on the button. The function OnCreateQuizClick(), located in my ViewModel, is triggerd and this function needs to clear my entry text, which i don't get for the moment.
I also tried to use a regular Command instead of using a RelayCommand, but also here it doesn't want to work.
EDIT: UNDERNEATH CODE WORKS FINE - GOT UPDATED
Code is used to clear entry text when clicking on a button from your ViewModel, implementing INotifyPropertyChanged Interface
.xaml - code
<Button x:Name="AddQuestionToQuiz" WidthRequest="200" Command="{Binding CreateQuizCommand}" Style="{StaticResource ButtonStyle}" Text="Add question to quiz"></Button>
ViewModel - code
internal class CreateQuizPageViewModel : INotifyPropertyChanged
{
// Quiz Name Input
public String QuizNameInput { get; set; }
private String quizQuestionInput = "";
public String QuizQuestionInput
{
get { return quizQuestionInput; }
set { quizQuestionInput = value; OnPropertyChanged(); }
}
public RelayCommand CreateQuizCommand { get; set; }
public CreateQuizPageViewModel()
{
CreateQuizCommand = new RelayCommand(OnCreateQuizClick);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnCreateQuizClick()
{
QuizQuestionInput = "";
}
}
EDIT: VIEWMODEL UPDATED
.xaml - code
<Button x:Name="AddQuestionToQuiz" WidthRequest="200" Command="{Binding CreateQuizCommand}" Style="{StaticResource ButtonStyle}" Text="Add question to quiz"></Button>
ViewModel - code
internal class CreateQuizPageViewModel : INotifyPropertyChanged
{
// Quiz Name Input
public String QuizNameInput { get; set; }
private String quizQuestionInput = "";
public String QuizQuestionInput
{
get { return quizQuestionInput; }
set { quizQuestionInput = value; OnPropertyChanged(); }
}
public RelayCommand CreateQuizCommand { get; set; }
public CreateQuizPageViewModel()
{
CreateQuizCommand = new RelayCommand(OnCreateQuizClick);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnCreateQuizClick()
{
QuizQuestionInput = "";
}
}
I am working to build an app which will use the code behind to supply new text via bindings when you click the next button. Each time I set the page to load on the virtual phone it times out the hot reload... The worst part is that I am not getting any errors either. Any ideas?
Content Page:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="List.MainPage">
<ContentPage.Content>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Label Text="{Binding TitleText}" />
<ScrollView VerticalOptions="FillAndExpand">
<StackLayout>
<Label Text="{Binding EngText}" />
<Label Text="{Binding ItText}" />
</StackLayout>
</ScrollView>
<Button Text="Next Page" Clicked="OnNavigateButtonClicked" />
</StackLayout>
</ContentPage.Content>
Code Behind:
using System;
using System.Collections.Generic;
using Xamarin.Forms;
namespace List
{
public partial class MainPage : ContentPage
{
List<MainPage> Contacts { get; set; }
public string TitleText { get; set; }
public string EngText { get; set; }
public string ItText { get; set; }
int ndx = 0;
public MainPage()
{
InitializeComponent();
Contacts = new List<MainPage>
{
// repeat this for as many contacts as you need
new MainPage
{
TitleText = "Title1",
EngText = "EngText1",
ItText = "ItText1"
},
new MainPage
{
TitleText = "Title2",
EngText = "EngText2",
ItText = "ItText2"
},
};
// display the first contact
BindingContext = Contacts[ndx];
}
void OnNavigateButtonClicked(object sender, EventArgs e)
{
// increment your index
ndx++;
// check that we haven't gone too far
if (ndx < Contacts.Count)
{
BindingContext = Contacts[ndx];
}
}
}
}
you are using the same class MainPage for your UI and your data. The constructor of MainPage creates 2 new instances of `MainPage, each of which call their constructor and create 2 more instances, which recurses forever until you use all the memory and crash
you need two different classes, one for your UI and one for your data
public class Data
{
public string TitleText { get; set; }
public string EngText { get; set; }
public string ItText { get; set; }
}
public partial class MainPage : ContentPage
{
List<Data> Contacts { get; set; }
int ndx = 0;
public MainPage()
{
InitializeComponent();
Contacts = new List<Data>
{
// repeat this for as many contacts as you need
new Data
{
TitleText = "Title1",
EngText = "EngText1",
ItText = "ItText1"
},
new Data
{
TitleText = "Title2",
EngText = "EngText2",
ItText = "ItText2"
},
};
I am using Media Plugin and everything worked fine until i have decided to move my logic to ViewModel.
This is my Xaml
<Frame BackgroundColor="LightGray" HasShadow="True">
<Image
x:Name="Photo"
Grid.Row="2"
HeightRequest="100"
Source="{Binding postViewModel.SelectedPhoto}"
VerticalOptions="Start"/>
</Frame>
My Binding to MasterViewModel
MasterPostsViewModel ViewModel;
protected override void OnAppearing()
{
base.OnAppearing();
BindingContext = ViewModel = new MasterPostsViewModel(Navigation);
}
My Master
class MasterPostsViewModel : BaseViewModel
{
public PostViewModel postViewModel { get; set; }
public CategoriesViewModel categoriesViewModel { get; set; }
public MasterPostsViewModel(INavigation navigation)
{
postViewModel = new PostViewModel();
categoriesViewModel = new CategoriesViewModel();
postViewModel = new PostViewModel(navigation);
}
}
Taking Picture in View Model
private MediaFile _selectedPhoto;
public MediaFile SelectedPhoto { get => _selectedPhoto; set => SetValue(ref
_selectedPhoto, value); }
private async Task TakePicture()
{
await Permission();
var imageSource = await DependencyService.Get<IMessage>().ShowActionSheet(AppResources.AlertPhoto, AppResources.AlertNewPhoto, AppResources.AlertGallery);
if (imageSource == AppResources.AlertNewPhoto)
{
var imageFileName = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions()
{
Name = $"{DateTime.UtcNow}.jpg",
DefaultCamera = Plugin.Media.Abstractions.CameraDevice.Rear,
PhotoSize = PhotoSize.Medium,
SaveToAlbum = true
});
if (imageFileName == null) return;
else
{
SelectedPhoto = imageFileName;
}
}
}
I can see tthe adress of the picture however the picture doesnt display on my xaml. I have tried to follow this
Bind Plugin.Media fromViewModel
But still didnt work. Please some suggestion on what am i doing wrong
I use you code and write a demo with binding a string, it works well. You can have a look at the code and may get some idea from it.
Code in xaml:
<StackLayout>
<!-- Place new controls here -->
<Label Text="{Binding postViewModel.SelectedPhoto}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Button Text="click me" Command ="{Binding postViewModel.NewCommand}"/>
</StackLayout>
Code behind:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
MasterPostsViewModel ViewModel;
protected override void OnAppearing()
{
base.OnAppearing();
BindingContext = ViewModel = new MasterPostsViewModel(Navigation);
}
}
class MasterPostsViewModel
{
public PostViewModel postViewModel { get; set; }
public MasterPostsViewModel(INavigation navigation)
{
postViewModel = new PostViewModel();
}
}
class PostViewModel : INotifyPropertyChanged
{
string _selectedPhoto;
public ICommand NewCommand { private set; get; }
public event PropertyChangedEventHandler PropertyChanged;
public PostViewModel()
{
SelectedPhoto = "default text";
NewCommand = new Command(TakePicture);
}
private void TakePicture()
{
SelectedPhoto = "test text After click button";
}
public string SelectedPhoto
{
set
{
if (_selectedPhoto != value)
{
_selectedPhoto = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedPhoto"));
}
}
}
get
{
return _selectedPhoto;
}
}
}
Sample project has been uploaded here.
I can get datatable after scan barcode but it not show when i bind it to sfDatagrid. what am i doing wrong. I think i call vm.TimSPTonKho.Execute(null); in .cs incorrectly
code xaml and .cs
<ContentPage.BindingContext>
<vm:vmBanHang_get_TTSanPham_ScanCode />
</ContentPage.BindingContext>
<StackLayout>
<Grid VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand">
<zxing:ZXingScannerView x:Name="scanView"
OnScanResult="scanView_OnScanResult"
IsScanning="True"
WidthRequest="200"
HeightRequest="300"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"/>
<zxing:ZXingDefaultOverlay TopText="Align the barcode within the frame"/>
</Grid>
<datagrid:SfDataGrid HorizontalOptions="Center" x:Name="datagrid"
AllowTriStateSorting="True"
ColumnSizer="Star"
ItemsSource="{Binding DataTableCollection}">
</datagrid:SfDataGrid>
</StackLayout>
this my xaml file
private void scanView_OnScanResult(Result result)
{
Device.BeginInvokeOnMainThread(async () =>
{
await DisplayAlert("Scanned result", "The barcode's text is " + result.Text + ". The barcode's format is " + result.BarcodeFormat, "OK");
var vm = new vmBanHang_get_TTSanPham_ScanCode();
vm.MaSanPham = result.Text;
vm.IDCuaHang = 1;
vm.TimSPTonKho.Execute(null);
});
}
my ViewModel.cs
class vmBanHang_get_TTSanPham_ScanCode : INotifyPropertyChanged
{
private ApiServices _apiServices = new ApiServices();
public int IDCuaHang { get; set; }
public string MaSanPham { get; set; }
public vmBanHang_get_TTSanPham_ScanCode()
{
DataTableCollection = _DataTableCollection;
}
public DataTable DataTableCollection
{
get { return _DataTableCollection; }
set
{
_DataTableCollection = value;
OnPropertyChanged();
}
}
public DataTable _DataTableCollection;
public ICommand TimSPTonKho
{
get
{
return new Command(async () =>
{
if (!string.IsNullOrEmpty(MaSanPham))
{
DataTableCollection = await _apiServices.get_TTSanPham_ScanCode(IDCuaHang, MaSanPham, Settings.Accesstoken);
}
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2. My second question, how can i get all values Datagrid send to Datatable ? do i have to implement it in xaml.cs or in viewmodel.
Thanks for helps
I found the solution. bind directly from xaml.cs
datagrid.ItemsSource = await _apiServices.get_TTSanPham_ScanCode(1, result.Text, Settings.Accesstoken);
instead of call Icommand in Viewmodel
public ICommand TimSPTonKho
{
get
{
return new Command(async () =>
{
if (!string.IsNullOrEmpty(MaSanPham))
{
DataTableCollection = await _apiServices.get_TTSanPham_ScanCode(IDCuaHang, MaSanPham, Settings.Accesstoken);
}
});
}
}
I have a UWP Page, containing a form with textboxes and a ListView control. The ListView control is bound to a collection of Products. And I want that the bound textboxes should show the information regarding the product selected in the listview.
public class Product: INotifyPropertyChanged
{
public int ProductID { get; set; }
private string name;
public string Name {
get { return name; }
set
{
if (name==value)
return;
name = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public Product(int pid, string name)
{
ProductID = pid;
Name = name;
}
}
}
The XAML is as below:
<TextBox x:Name="txtProductId" Grid.Row="1" Grid.Column="1"
Text="{x:Bind CurrentProduct.ProductID}"/>
<TextBox x:Name="txtProductName" Grid.Row="2" Grid.Column="1"
Text="{x:Bind CurrentProduct.Name}" />
<ListView x:Name="lstProducts" Grid.Row="3" Grid.ColumnSpan="2"
ItemsSource="{x:Bind ProductList}"
SelectedItem="{x:Bind CurrentProduct, Mode=TwoWay}"
ItemTemplate="{StaticResource lstDataTemplate}"
>
</ListView>
The following code executes on Page_Loaded:
CurrentProduct = Products[0];
DataContext = CurrentProduct;
The ListView is bound to ProductList (Type ObservableCollection). I've noticed by executing the app in single step mode, the value of CurrentProduct is changing, but I think since it is the reference and not the property of the DataContext that changes, the PropertyChanged event is never fired and the TextBox doesn't get updated to show the name of the CurrentProduct.
I don't know how to proceed, any help would be appreciated.
The X:Bind default mode is OneTime, in your case, you need to set the mode to OneWay.
I made a code sample for your reference:
<TextBox x:Name="txtProductId" Grid.Row="1" Grid.Column="1"
Text="{x:Bind CurrentProduct.ProductID,Mode=OneWay}"/>
<TextBox x:Name="txtProductName" Grid.Row="2" Grid.Column="1"
Text="{x:Bind CurrentProduct.Name,Mode=OneWay}" />
<ListView x:Name="lstProducts" Grid.Row="3" Grid.ColumnSpan="2"
ItemsSource="{x:Bind ProductList}"
SelectedItem="{x:Bind CurrentProduct, Mode=TwoWay}"
>
</ListView>
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 10; i++)
{
ProductList.Add(new Product(i, "name " + i));
}
}
public ObservableCollection<Product> ProductList { get; set; } = new ObservableCollection<Product>();
private Product _CurrentProduct = new Product(100,"test");
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,new PropertyChangedEventArgs(PropertyName));
}
}
public Product CurrentProduct
{
get { return _CurrentProduct; }
set
{
if (_CurrentProduct != value)
{
_CurrentProduct = value;
RaisePropertyChanged("CurrentProduct");
}
}
}
}
public class Product : INotifyPropertyChanged
{
public int ProductID { get; set; }
private string name;
public string Name
{
get { return name; }
set
{
if (name == value)
return;
name = value;
RaisePropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public Product(int pid, string name)
{
ProductID = pid;
Name = name;
}
public override string ToString()
{
return Name;
}
}