Updating content between tabs binded by Preference - xamarin.forms

I am using a tabbed app and I have a button on one tab, when the user clicks then it increments a Preference and I want to update its value on another tab that is Binded to a label. I tried to experiment with MVVM but I couldnt figure it out.
Page 1 view:
public partial class Page1View: ContentPage
{
public Page1View()
{
InitializeComponent();
}
private void Button_Clicked(object sender, EventArgs e)
{
Preferences.Set("Total", Preferences.Get("Total", 0)+1);
}
}
Page 2 Viewmodule:
public class Page2ViewModule : INotifyPropertyChanged
{
public int Total
{
get => Preferences.Get(nameof(Total), 0);
set
{
if (Preferences.Get(nameof(Total),0) == value)
return;
Preferences.Set(nameof(Total), value);
OnPropertyChanged(nameof(Total));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here's what I done with MessagingCenter. On the button click event I sent a message like this:
private void Button_Clicked(object sender, EventArgs e)
{
Preferences.Set("Total", Preferences.Get("Total", 0)+1);
MessagingCenter.Send<Page>(this, "test");
}
In the Page2 view module I added a constructor that subscribes to message:
public Page2ViewModule()
{
MessagingCenter.Subscribe<Page>(this, "test", (p) =>
{
Total = Preferences.Get(nameof(Total), 0);
});
}
And that still doesn't work. But I'm not sure would this be the best way to do it? Or is there a more efficient way?

For Jason's question: Page2 already been created and the Subscribe method execute before you call Send, I test at my side, Page2 is been created and the Subscribe method execute after you call Send, so I think it works fine using MessagingCenter to notify each page when the Preference has been updated.
Tab one Page.
private void btn1_Clicked(object sender, EventArgs e)
{
Preferences.Set("Total", Preferences.Get("Total", 0) + 1);
label1.Text = Preferences.Get("Total", 0).ToString();
MessagingCenter.Send<Page>(this, "test");
}
Tab two page.
<ContentPage.Content>
<StackLayout>
<Label Text="{Binding Total}" />
</StackLayout>
</ContentPage.Content>
public partial class Tab2 : ContentPage
{
public Tab2()
{
InitializeComponent();
this.BindingContext = new Page2ViewModule();
}
}
public class Page2ViewModule : ViewModelBase
{
private int _Total;
public int Total
{
get { return _Total; }
set
{
_Total = value;
RaisePropertyChanged("Total");
}
}
public Page2ViewModule()
{
MessagingCenter.Subscribe<Page>(this, "test", (p) =>
{
Total = Preferences.Get("Total", 0);
});
}
}

Related

Clear Datepicker's value after clicking the submit button in xamarin form

My problem is, the data which is date entered by user is doesn't after submit button. So I have fields in my registration page and A button to save in my database.
This is what I've tried.
//My Datepicker Design
`
<local:BirthdayDatePickerControl
TextColor="Black"
x:Name="entryField_DateOfBirth"
/>
`
The purpose that I create a custom control in my datepicker is to put an placeholder iny my datepicker field.
//my Birthdaypickercontrol.cs
`
public class BirthdayDatePickerControl : DatePicker
{
public event EventHandler ClearRequested;
// for my placeholder "birthdate"
public static readonly BindableProperty EnterTextProperty = BindableProperty.Create(propertyName: "Placeholder", returnType: typeof(string), declaringType: typeof(BirthdayDatePickerControl), defaultValue: default(string));
public string Placeholder { get; set; }
//function to clear data of my datepicker input
public void clear()
{
if (ClearRequested != null)
{
ClearRequested(this, EventArgs.Empty);
}
}
}
`
In my project.android, I create a birthday renderer.cs
//so this is my code
`
[assembly: ExportRenderer(typeof(BirthdayDatePickerControl), typeof(BirthdayDatePickerRenderer))]
public class BirthdayDatePickerRenderer : DatePickerRenderer
{
public BirthdayDatePickerRenderer(Context context) : base(context)
{
}
EditText editText;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.DatePicker> e)
{
base.OnElementChanged(e);
//code for placeholder
if (Control != null)
{
Control.Text = "Birth Date";
}
//end here
//code start here for clearing the data in datepicker input field
editText = Control as EditText;
if (e.NewElement != null)
{
BirthdayDatePickerControl bdaydatePickerControl = e.NewElement as BirthdayDatePickerControl;
bdaydatePickerControl.ClearRequested += DatePickerControl_ClearRequested;
}
//end here
}
public void DatePickerControl_ClearRequested(object sender, EventArgs e)
{
editText.Text = string.Empty;
}
}
`
The codes I pasted will anyway, but..
Assuming in the onload of my registration page, The UI will be like this( pic for reference and ctto to google). After user choose birthdate, example 12/1/22 and hit submit button(all data save in to database). The problem is the placeholder "birthdate" remove/disappear Like this, then if I click the datepicker input field to check the date, the date is still pointing in 12/1/22. What I expected is after performing the ClearData(), the date should be reset in today's date.
//this is my ClearData() function
`
public void ClearData()
{
entryField_DateOfBirth.clear();// this is what I tried and got an bad ouput
}
`
You said:
What I expected is after performing the ClearData(), the date should be reset in today's date.
public void DatePickerControl_ClearRequested(object sender, EventArgs e)
{
editText.Text = string.Empty;
}
Change to this:
public void DatePickerControl_ClearRequested(object sender, EventArgs e)
{
BirthdayDatePickerControl birthdayDate = sender as BirthdayDatePickerControl;
birthdayDate.Date = DateTime.Now;
editText.Text = "Birth Date";
}

Dependency service event

I am trying to call the event handler from droid side, however it doesn't get fired. everyone else works however this event doesn't get fired. I am not sure what am I doing wrong. I have my dependency service registered at MainActivity and set dependency in my service.
public partial class MainPage : ContentPage
{
DocumentResults results;
IScanService scanService;
public MainPage()
{
InitializeComponent();
results = new DocumentResults();
BindingContext = new MainPageViewModel();
System.Diagnostics.Debug.WriteLine("test cw");
Console.WriteLine("test cw");
scanService = DependencyService.Get<IScanService>();
scanService.ResultsParsedEvent += (s, ev) => { ResultsParsed(null, ev); };
}
void Button_Clicked(System.Object sender, System.EventArgs e)
{
scanService.ScanService();
if (!String.IsNullOrWhiteSpace(test))
{
scanService.Parsing(test);
}
}
private void ResultsParsed(DocumentResults results,EventArgs e)
{
Console.WriteLine("update ");
testLbl.Text = results.Name;
}
}
My interface
public interface IScanService
{
event EventHandler ResultsParsedEvent;
string ScanService();
void Parsing(string test);
void resultsParsed(DocumentResults results, EventArgs e);
}
Droid implementation
public class RegService : IScanService
{
public event EventHandler ResultsParsedEvent;
DocumentResults results;
public string Test;
public String ScanService()
{
Test = "scan";
return Test;
}
public void Parsing(string test)
{
Test = "parsing";
var results= new DocumentResults();
results.Name = Test;
Thread thread1 = new Thread(() => resultsParsed(results,null));
System.Threading.Thread.Sleep(10);
resultsParsed(results,null);
}
public void resultsParsed(DocumentResults results, EventArgs e)
{
ResultsParsedEvent?.Invoke(results, e);
}
}

How send data (selectItem of listView) from xaml to ViewModel

I want get data selected in my viewModel
this is my xaml, but i donot know how solved my xaml, because this is bad, how use my behavior here in my xaml?
<ListView.Behaviors>
<behaviors:ItemTappedBehavior EventName="ItemSelected">
<behaviors:InvokeCommandAction Command="{Binding SelectedTagChanged}" />
</behaviors:ItemTappedBehavior>
</ListView.Behaviors>
<ListView ItemsSource="{Binding actors}" ItemTapped="ListView_ItemTapped">
in my behindcode
public partial class ActorsView : ContentPage
{
public AccountsView()
{
InitializeComponent();
}
async void ListView_ItemTapped(object sender, ItemTappedEventArgs e)
{
Actor selectedItem = (Actor)e.Item;
Console.WriteLine("WORK"+ Actor.Name);
}
but i want to get in my viewModel, dont in my behind code
i've seen to solved is with commands, or behaviors
this is my viewModel:
public class ActorsViewModel : ViewModelBase
{
public List<Actor> actors { get; set; }
public AccountsViewModel(IActorManager actorManager)
: base()
{
Edit, i am using commands but i dont know how use the answer from John Livermore, i want to use and show the console Console.WriteLine("ROW");.
public class ItemTappedBehavior : Behavior<ListView>
{
public ICommand Command { get; set; }
protected override void OnAttachedTo(ListView bindable)
{
base.OnAttachedTo(bindable);
}
protected override void OnDetachingFrom(ListView bindable)
{
base.OnDetachingFrom(bindable);
}
public Command SelectedTagChanged
{
get
{
return new Command(row =>
{
Console.WriteLine("ROW");
});
}
}
}
Since you had asked for a simpler Behavior for ListView selection changed. Please use the following:
Following class is inherited from BehaviorBase which takes care of BindingContext allocation.
On the OnAttachedTo override the ItemSelected event is hooked.
On the OnDetachingFrom override the ItemSelected event is unhooked.
Command bindable property Binds to the Command in ViewModel
In the event the e.SelectedItem is passed to the Command. (Change
it to whatever value you wish to pass)
public class SelectionChangedEventBehavior : BehaviorBase<ListView>
{
public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(SelectionChangedEventBehavior), null);
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
protected override void OnAttachedTo(BindableObject bindable)
{
base.OnAttachedTo(bindable);
AssociatedObject.ItemSelected += AssociatedObject_ItemSelected;
}
protected override void OnDetachingFrom(ListView bindable)
{
base.OnDetachingFrom(bindable);
AssociatedObject.ItemSelected -= AssociatedObject_ItemSelected;
}
private void AssociatedObject_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (Command != null)
{
Command.Execute(e.SelectedItem);
}
}
}
Usage
Xaml
<ListView ItemsSource="{Binding Collection}">
<ListView.Behaviors>
<local:SelectionChangedEventBehavior Command="{Binding SelectionCommand}" />
</ListView.Behaviors>
</ListView>
ViewModel
public class ViewModel : INotifyPropertyChanged
{
public Command<object> SelectionCommand { get; set; }
public ViewModel()
{
SelectionCommand = new Command<object>(OnSelection);
}
private void OnSelection(object item)
{
var selectedActor = (item as Actor);
}
}
Hope this could be useful for you. If possible use the EventToCommandBehavior as whole, just because you can reuse it for any control and any event.
In your XAML do the following:
Set your <ListView ItemsSource="{Binding actors}" ItemTapped="ListView_ItemTapped">
to be <ListView ItemsSource="{Binding actors}" SelectedItem = "{Binding SelectedActor, Mode=TwoWay}>
In your view model which your current XAML is bound to, create two new variables as so:
private Actor selectedActor;
public Actor SelectedActor {
get {return selectedActor;}
set {
selectedActor = value;
if(selectedActor != null) {//set properties here if needed}
OnPropertyChanged("SelectedActor"); //or however you raise your events, this is a
//custom method i've made
}
Now, where we define our actors item source, we will also define what a selectedActor is as so:
//get your actors, loop over them and add them to the list of actors
var actorItem = new Actor();
actors.Add(actorItem);
//outside of list raise the property changed event on your actors list.
OnPropertyChanged("actors")
That is a the pattern that i've used at work to get my selected item. If you then need to do something with it you could define it within the SelectedActor variable. For us our app is a hybrid between old xamarin (.Android and .iOS) and .Forms, so we pass Actions from the native level to the view model (ex: loading the summary page for the selected item)

how to keep a modal popup open after postback

//this is my interface
public interface IPopupGrid
{
void PopupClosed(string key);
void PopupGridPageChanging(string key);
void PopupFilterButtonClicked(string key);
void PopupOkButtonClicked(string key);
void PopupGridSorting(string key);
}
//this is the class that implements the interface
//on buton click this loads a grid in modal popup extender
public partial class CustomerChargesMaster : IPopupGrid
{
public void PopupClosed(string key)
{
if (key == "Customer")
CloseCustomerPopup();
}
public void PopupGridPageChanging(string key)
{
if (key == "Customer")
ShowCustomerPopup();
}
public void PopupFilterButtonClicked(string key)
{
if (key == "Customer")
ShowCustomerPopup();
}
public void PopupOkButtonClicked(string key)
{
if (key == "Customer")
CustomerSelected();
}
public void PopupGridSorting(string key)
{
if (key == "Customer")
ShowCustomerPopup();
}
private void CloseCustomerPopup()
{
CustomerPopupExtender.Hide();
}
private void ShowCustomerPopup()
{
CustomerPopupExtender.Show();
}
protected void SelectCustomerButton_Click(object sender, EventArgs e)
{
CustomerPopup.CssClass = "";
CustomerPopupGrid.GridMode = PopupGridMode.SingleSelect;
CustomerPopupGrid.LoadPopupControl("Customer", this);
CustomerPopupExtender.Show();
}
private void CustomerSelected()
{
}
}
//code inside user control
public partial class GenericPopupGrid : System.Web.UI.UserControl
{
public void LoadPopupControl(string key, IPopupGrid caller)
{
if (string.IsNullOrEmpty(key))
return;
_key = key;
ViewState["PopupGridKey"] = key;
_caller = caller;
//Session["Caller"] = caller;
listModel = ModelFactory.GetPopupGridModel(_key);
if (listModel == null)
{
return;
}
PopupHeaderLabel.Text = listModel.GridTitle;
BindGenericPopupGridView();
GenericPopupGridView.BottomPagerRow.Visible = true;
}
protected void FilterOkButton_Click(object sender, EventArgs e)
{
//calling the caller class method to keep the popup open
_caller.PopupOkButtonClicked(_key);
}
}
I have a web user control with grid which I use to load different data depending upon the key passed
This is called as a popup using modal popup extender from a aspx page
Now doing any postback will close the grid
so i am using the interface to call the method in the caller page, which will keep the popup open
but this is not working as intended
If i add an EventHandler in the UserControl and attach a method in the aspx page then the popup remains open
Is there any other easier way to do this? I do not want to write the code to attach a method to even handler and the method itself in all the aspx pages that calls the popup.
Code is as above

Getting data back from composite control added to placeholder on postback

I'm trying to create a factory pattern based on the CompositeControl class.
Everything is working fine. Except as I understand it I have to add the control back onto the placeholder control on my actual page. I've searched around for examples and got one to almost work expect it only worked on the first postback.
I've created a sample here and I'll post all the code here.
Code on my webpage, the most important here is I guess on OnInit where I'm trying to add the control back onto the placeholder, I guess this is what I might be doing wrong.
using SampleControls;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
//Setup First Control
Session.Clear();
BaseCompositeControl ltb = new LabelTextBox();
PlaceHolder1.Controls.Add(ltb);
Session.Add("Control", ltb);
}
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
// Check if the post back and recreate the control
if (IsPostBack)
{
int c = this.Form.Controls.Count;
for (int i = 0; i < Session.Count; i++)
{
if (Session[i].ToString().Contains("Control"))
{
this.PlaceHolder1.Controls.Add((BaseCompositeControl)(Session[i]));
}
}
}
}
protected void Button1_Click(object sender, EventArgs e)
{
//Get Postback Date
BaseCompositeControl oltb = (BaseCompositeControl)this.PlaceHolder1.Controls[0];
lblPstBck.Text = oltb.Text;
this.PlaceHolder1.Controls.Clear();
Session.Clear();
//Load next Control
BaseCompositeControl ltb = new LabelCheckBox();
PlaceHolder1.Controls.Add(ltb);
Session.Add("Control", ltb);
}
}
This is composite control classes, I don't know if I have to do something here to handle the viewstate or how?
public abstract class BaseCompositeControl : CompositeControl
{
protected string _Title;
public abstract string Text
{
get;
set;
}
public string Title
{
get { EnsureChildControls();
return _Title; }
set { EnsureChildControls();
_Title = value; }
}
protected override void CreateChildControls()
{
// Clears child controls
Controls.Clear();
// Build the control tree
CreateControlHierarchy();
ClearChildViewState();
}
protected abstract void CreateControlHierarchy();
}
TextBox control
public class LabelCheckBox : BaseCompositeControl
{
protected CheckBox _CheckBox;
public override string Text
{
get
{
EnsureChildControls();
return _CheckBox.Checked.ToString(); ;
}
set
{
EnsureChildControls();
_CheckBox.Checked = Convert.ToBoolean(value);
}
}
protected override void CreateControlHierarchy()
{
_CheckBox = new CheckBox();
Label l = new Label();
// Configure controls
l.Text = "Second Control";
// Connect to the parent
Controls.Add(l);
Controls.Add(_CheckBox);
}
}
Checkbox control
public class LabelTextBox : BaseCompositeControl
{
protected TextBox _Text;
public override string Text
{
get
{
EnsureChildControls();
return _Text.Text;
}
set
{
EnsureChildControls();
_Text.Text = value;
}
}
protected override void CreateControlHierarchy()
{
Label l = new Label();
_Text = new TextBox();
// Configure controls
l.Text = "First Control";
// Connect to the parent
Controls.Add(l);
Controls.Add(_Text);
}
}
I asked this a long time ago,
But I think this is how you should do it. Using a factory pattern for user controls.
http://weblogs.asp.net/sfeldman/archive/2007/12/17/factory-pattern-for-user-controls.aspx

Resources