Xaramin form -calling variable from other .cs file - xamarin.forms

I am doing a quiz game in Xaramin. forms. and for the score function. if the user got a correct answer, I want the score will add 1.but in my case even the give the correct answer, the score is not adding.
I am also trying to bind to the "score" variable to a label. I want to know if i put a correct code or not.
Button
private void submit_Clicked(object sender, EventArgs e)
{
string answer = this.answer.Text;
string canswer = "correct";
if (answer != null)
{
string ranswer = answer.Replace(" ", string.Empty);
if (ranswer.ToLower() == canswer)
{
DisplayAlert("GoodJob", "You got the correct answer", "OK");
bindingModel b = new bindingModel();
b.score++;
(sender as Button).IsEnabled = false;
}
else
{
DisplayAlert("Unfortunately", "Your answer is wrong", "OK");
(sender as Button).IsEnabled = false;
}
}
}
ViewModel
public class bindingModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int displayScore => Score;
public int score = 0;
void OnPropertyChanged(int score)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(score.ToString()));
}
public int Score
{
get => score;
set
{
if (score != value)
{
score = value;
OnPropertyChanged(score);
}
}
}
}
Model
<Label Text="{Binding Score}"/>

in your page constructor, keep a reference to your VM
bindingModel VM;
// this is your constructor, the name will match your page name
public MyPage()
{
InitializeComponent();
this.BindingContext = VM = new bindingModel();
...
}
then in your event handler, you do NOT need to create a new bindingModel
// update the Count on the VM
VM.Count++;

Answer
There's two things broken here:
You are re-initializing your ViewModel instead of referencing the same instance
You are passing the wrong value into PropertyChangedEventArgs
1. Referencing the View Model
You are re-initializing the ViewModel every time by calling bindingModel b = new bindingModel();
Lets initialize the ViewModel once, store it as a field, set it as the BindingContext for our ContentPage, and reference that field in submit_Clicked
public partial class QuizPage : ContentPage
{
readonly bindingModel _bindingModel;
public QuizPage()
{
_bindingModel = new bindingModel();
BindingContext = _bindingModel;
}
private async void submit_Clicked(object sender, EventArgs e)
{
string answer = this.answer.Text;
string canswer = "correct";
Button button = (Button)sender;
if (answer != null)
{
string ranswer = answer.Replace(" ", string.Empty);
if (ranswer.ToLower() == canswer)
{
await DisplayAlert("GoodJob", "You got the correct answer", "OK");
_bindingModel.score++;
button.IsEnabled = false;
}
else
{
await DisplayAlert("Unfortunately", "Your answer is wrong", "OK");
button.IsEnabled = false;
}
}
}
}
2. PropertyChangedEventArgs
You need to pass in the name of the property to PropertyChangedEventArgs.
They way PropertyChanged works is that it announces the name of the property that has changed. In this case, it needs to broadcast that the Score property has changed.
Let's use nameof(Score) to pass in the string "Score" to PropertyChangedEventArgs:
void OnScorePropertyChanged()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(displayScore)));
}
public int Score
{
get => score;
set
{
if (score != value)
{
score = value;
OnScorePropertyChanged();
}
}
}

Related

Create table in SQLite with generic class 'Quantity' as a picker - ERROR not a valid type for SQLite DB

Morning All,
xamarin forms - populate picker in MVVM from SQLite DB issue
So...Ive used the following post
https://www.c-sharpcorner.com/article/populate-picker-using-mvvm/
to help me include a populated picker using MVVM in my project, using a picker to allowing the user to select a 'Quantity' to order for each product (which all works fine as its populated from code, but now refactoring to load from SQLite..please see current code below)..
//ProductModel
public class ProductModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _ProductId;
private string _BrandName;
public ObservableCollection<Quantity> _ListQuantites;
private Quantity _selectedQuantity;
//ETC have removed the other properties for sake of this Q
//Constructor
public ProductModel()
{
//Subscription
this.PropertyChanged += OnPropertyChanged;
}
[PrimaryKey, AutoIncrement]
public int ProductId
{
get { return _ProductId; }
set
{
if (_ProductId == value) return;
_ProductId = value;
OnPropertyChanged();
}
}
public ObservableCollection<Quantity> ListQuantites
{
get
{
return _ListQuantites;
}
set
{
_ListQuantites = value;
OnPropertyChanged();
}
}
public Quantity SelectedQuantity
{
get
{
return _selectedQuantity;
}
set
{
if (value == null)
{
_selectedQuantity = _selectedQuantity;
}
else
{
_selectedQuantity = value;
OnPropertyChanged();
}
}
}
//OnPropertyChanged
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SelectedQuantity))
{
//test quantity amount
}
}
// [NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Up Until now I was populating the ProductModel with its picker, from the constructor of the ProductViewModel page with:
WineList = new ObservableCollection<ProductModel>();
WineList.Add(new ProductModel { ProductId = 1, BrandName = "Mc guigans", Grape = "Red", ListQuantites = List_Quantites, Image = "W.png", Description = "Fruity Flav", Size="700ml", Price = 10.00M, SubTotalForItem = 0.00M, Genre = "Wine" });
//etc...and so on
//then
List_Quantites = PickerService.GetQuantitiesForProductPage();
//Picker service
public static ObservableCollection<QuantityModel> GetQuantitiesForProductPage()
{
var quantities = new ObservableCollection<QuantityModel>()
{
new QuantityModel() {Key=1, Value="0"},
new QuantityModel() {Key=2, Value="1"},
new QuantityModel() {Key=3, Value="2"},
new QuantityModel() {Key=4, Value="3"}
//etc
};
return quantities;
}
//XAML
<Picker Grid.Column="3" Grid.Row="0" Title=" " VerticalOptions="Center" x:Name="productPicker" VerticalTextAlignment="Center" HorizontalOptions="EndAndExpand" ItemsSource="{Binding ListQuantites}" ItemDisplayBinding="{Binding Value}" SelectedIndexChanged="QuantityChanged" SelectedItem ="{Binding SelectedQuantity}"/>
So...yeah as I said all works fine and dandy...But now I would like to load the ProductModel list from a table in SQLite....I have already used SQLite basic CRUD operations to create, load, view, update, edit...etc for orders made...so this is also working...the problem I seem to be having is creating the table, it is failing when I try to create an entry in the table with 'Quantity'....obviously...so I have changed the code...change 'Quantity' to object, then the plan being to populate this on the VM...but this also didnt work...
found another post relating to this:
https://forums.xamarin.com/discussion/2546/create-table-in-xamarin-throws-exception-because-of-generic-list
So 'Quantity' class is not a valid type for SQLite DB value...but turns out neither is object...has anyone idea for a work around for this...or some advice on how to refactor to resolve this?
any help or point in the right direction is appreciated thank Y

How to pass value from dependency service to shared code

I am trying to get the last call duration on my xamarin.forms app. On android part I am using dependency service.I can get the call duration. How to pass the duration to shared code back?
My Implementation on Android
class Dialer : ICallerDialer
{
public void GetCallLogs()
{
string queryFilter = String.Format("{0}={1}", CallLog.Calls.Type, (int)CallType.Outgoing);
string querySorter = String.Format("{0} desc ", CallLog.Calls.Date);
ICursor queryData1 = Android.App.Application.Context.ContentResolver.Query(CallLog.Calls.ContentUri, null, queryFilter ,null, querySorter);
int number = queryData1.GetColumnIndex(CallLog.Calls.Number);
int duration1 = queryData1.GetColumnIndex(CallLog.Calls.Duration);
if (queryData1.MoveToFirst() == true)
{
String phNumber = queryData1.GetString(number);
String callDuration = queryData1.GetString(duration1);
How to pass this to Shared code back?
}
return;
}
}
My Interface
public interface ICallerDialer
{
void GetCallLogs();
}
Dependency call when button click
async void btnCall_Clicked(object sender, System.EventArgs e)
{
DependencyService.Get<ICallerDialer>().GetCallLogs();
//How to get duration here?
}
Any help is appreciated.
Just change the return type of your method to string type.
class Dialer : ICallerDialer
{
public string GetCallLogs()
{
string queryFilter = String.Format("{0}={1}", CallLog.Calls.Type, (int)CallType.Outgoing);
string querySorter = String.Format("{0} desc ", CallLog.Calls.Date);
ICursor queryData1 = Android.App.Application.Context.ContentResolver.Query(CallLog.Calls.ContentUri, null, queryFilter ,null, querySorter);
int number = queryData1.GetColumnIndex(CallLog.Calls.Number);
int duration1 = queryData1.GetColumnIndex(CallLog.Calls.Duration);
if (queryData1.MoveToFirst() == true)
{
String phNumber = queryData1.GetString(number);
String callDuration = queryData1.GetString(duration1);
return callDuration;
}
return string.Empty;
}
}
Interface
public interface ICallerDialer
{
string GetCallLogs();
}
Dependency call when button click
async void btnCall_Clicked(object sender, System.EventArgs e)
{
var duration = DependencyService.Get<ICallerDialer>().GetCallLogs();
}

User Control's child controls not getting instantiated

public partial class ChatUserControl : System.Web.UI.UserControl
{
UserChatClass ucc = new UserChatClass();
public ChatUserControl()
{
lblChatFriend = new Label();
txtChatMessage = new TextBox();
imgFriend = new Image();
rpChatMessages = new Repeater();
}
public string ChatFriend { get { return this.lblChatFriend.Text; } set { this.lblChatFriend.Text = value; } }
public string imgFriendUrl { get { return this.imgFriend.ImageUrl; } set { this.imgFriend.ImageUrl = value; } }
public object rpChatDataSource { get { return this.rpChatMessages.DataSource; } set { this.rpChatMessages.DataSource = value; } }
public Repeater rpChatMessagesToBind { get { return this.rpChatMessages; } set { this.rpChatMessages = value; } }
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
ChatUserControl user1 = new ChatUserControl();
divChatUserControlCover.Controls.Add(user1);
}
}
private void BindUserControls()
{ ChatUserControl user1 = divChatUserControlCover.Controls[1] as ChatUserControl;
user1.ChatFriend = row["username"].ToString();
user1.imgFriendUrl = "../../HttpImageHandler.jpg?username=" + row["username"].ToString();
DataSet dsCM = ucc.GetChatMessages(Session["username"].ToString(), row["username"].ToString());
user1.rpChatDataSource = dsCM;
user1.DataBindForRpChatMessagesToBind();
user1.Visible = true;
}
Master.aspx
<div id="divChatUserControlCover" runat="server">
</div>
Ok I have edited the code and now I have created properties. How do I call the DataBind method for rpChatMessages? I also cant see my usercontrol on page. Why
I'm not sure if your trying to reference the first label or second label. If its the second lable you can't just do chatMessage. you would have to do
((Label)rpChatMessages.FindControl("chatMessage")) due to scope of controls.
When you reference a component inside another component (ie Repeater) the child component no longer belongs to the document (implied this) but rather belongs to the control, ie
this.rpChatMessages { chatMessage }
I think you are just trying to pass a value to one control inside a UserControl if this is correct, declare a public property like this:
ASCX code behind
public string MyProperty
{
get
{
return this.lbl.Text;
}
set
{
this.lbl.Text = value;
}
}
Setting the value to the UserControl
private void BindUserControls()
{
ChatUserControl user1 = divChatUserControlCover.Controls[1] as ChatUserControl;
user1.MyProperty = row["username"].ToString();
Setting the value in the page markup
<uc1:ChatUserControl MyProperty='<%# Eval("some field") %>' ...
Edit 1
Remove that line
public object rpChatDataSource { get { return this.rpChatMessages.DataSource; } set { this.rpChatMessages.DataSource = value; }
And instead add a method
public void BindMyRepeaterOrWhatever(IEnumerable<Yourentity> data)
{
this.myDataBoundControl.DataSource = data;
this.myDataBoundControl.DataBind();
}
You can change the IEnumerable<Yourentity> data for object data but if you can pass a strongly typed enumeration would be better
To my surprise I found why my user control's child controls dont get instantiated. Its because ChatUserControl user1 = new ChatUserControl() doesnt get its child controls initialized.
The proper way to create a new intance of user control is this way....
ChatUserControl user1 = (ChatUserControl)Page.LoadControl("~/ChatUserControl.ascx");

Moq VerifySet(Action) strange behaviour

I use Moq to test some behaviour. I want to verify set view's model property as new instance of Message class:
[TestFixture]
public class MessageFormPresenterTests
{
private NameValueCollection queryString;
private Mock<IDatabase> mockDatabase;
private Mock<HttpContextBase> mockHttpContext;
private Mock<HttpRequestBase> mockRequest;
private Mock<HttpResponseBase> mockResponce;
private Mock<IMessageFormView> mockView;
private MessageFormPresenter presenter;
[SetUp]
public void SetUp()
{
this.queryString = new NameValueCollection();
this.mockDatabase = new Mock<IDatabase>();
this.mockHttpContext = new Mock<HttpContextBase>();
this.mockRequest = new Mock<HttpRequestBase>();
this.mockResponce = new Mock<HttpResponseBase>();
this.mockView = new Mock<IMessageFormView>();
this.mockHttpContext.SetupGet(c => c.Request).Returns(this.mockRequest.Object);
this.mockHttpContext.SetupGet(c => c.Response).Returns(this.mockResponce.Object);
this.mockRequest.SetupGet(r => r.QueryString).Returns(this.queryString);
this.presenter = new MessageFormPresenter(this.mockView.Object) { Database = this.mockDatabase.Object, HttpContext = this.mockHttpContext.Object };
}
[Test]
public void ViewLoad_QueryStringNotHasMessageIdParameter_PopulateViewModelAsNewMessage()
{
// Act
this.mockView.Raise(v => v.Load += null, new EventArgs());
// Assert
this.mockView.VerifySet(v => v.Model = It.Is<Message>(m => m.Id == 0));
}
}
But this test pass even if i not write this functionality in my MessageFormPresenter. When i set breakpoint to action call (v.Model = It.Is(m => m.Id == 0)) and debug my test, Model property returned as "Castle.Proxies.MessageProxy" type and has all properties with default values (value of Id property is 0, of course).
Why is this happening? I don't setup anything to return and Moq should return null by default.
P.S. Excuse me for my poor english. I hope you understand what i'm talking about)
UPDATE1: MessageFormPresenter class:
public class MessageFormPresenter : Presenter<IMessageFormView>
{
private IDatabase database;
public MessageFormPresenter(IMessageFormView view)
: base(view)
{
View.Load += this.View_Load;
}
public IDatabase Database
{
get { return this.database ?? (this.database = DatabaseFactory.DatabaseInstance); }
set { this.database = value; }
}
private void View_Load(object sender, EventArgs e)
{
int messageId;
var messageParam = Request.QueryString[QueryParamNames.MessageId];
if (messageParam == null)
{
View.Model = new Message();
return;
}
if (!View.IsAdmin)
{
Response.Redirect(PageUrls.AccessDenied, true);
return;
}
if (int.TryParse(
messageParam,
NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite,
CultureInfo.InvariantCulture,
out messageId))
{
View.Model = this.Database.Single<Message>(messageId);
}
else
{
Response.Redirect(PageUrls.DefaultPage, true);
}
}
}
Base class Presenter<IMessageFormView> provided by WebFormsMvp framework.
UPDATE2: If i use strict behaviour TargetInvocationException will be thrown in base constructor of MessageFormPresenter, even if i setup all properties of Mock<IMessageFormView>.

Save selected items when using caliburn.micro /Telerik RadGridView /Silverlight

I am using Caliburn micro(1.3)/MVVM and Silverlight. When I update the itemsource RadGridView, I lose the selected items. I found a blog about implementing a behavior to save the selected items when you are implementing MVVM. I can get the selected items, but I cannot set them back once the itemsource is refreshed. Can someoneshow me how to implement this using caliburn.micro and the RadGridVIew? I think the best way to go is to create a caliburn micro convention, but I can only find a reference for creating a convention for selectedItem, not selectedItems.
Can someone show me how to accomplish this? I tried the following, but it does not work.
private static void SetRadGridSelecteditemsConventions()
{
ConventionManager
.AddElementConvention<DataControl>(DataControl.ItemsSourceProperty, "SelectedItem", "SelectionChanged")
.ApplyBinding = (viewModelType, path, property, element, convention) =>
{
ConventionManager.SetBinding(viewModelType, path, property, element, convention, DataControl.ItemsSourceProperty);
if (ConventionManager.HasBinding(element, DataControl.SelectedItemProperty))
return true;
var index = path.LastIndexOf('.');
index = index == -1 ? 0 : index + 1;
var baseName = path.Substring(index);
foreach (var selectionPath in
from potentialName in ConventionManager.DerivePotentialSelectionNames(baseName)
where viewModelType.GetProperty(potentialName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) != null
select path.Replace(baseName, potentialName))
{
var binding = new Binding(selectionPath) { Mode = BindingMode.TwoWay };
BindingOperations.SetBinding(element, DataControl.SelectedItemProperty, binding);
}
return true;
};
}
Thanks,
Stephane
You should use a behavior for this since the SelectedItems property is readonly.
Telerik has an example for this, only the example is not specific for caliburn.micro.
If you add the following class to your project:
public class MultiSelectBehavior : Behavior<RadGridView>
{
public INotifyCollectionChanged SelectedItems
{
get { return (INotifyCollectionChanged)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(INotifyCollectionChanged), typeof(MultiSelectBehavior), new PropertyMetadata(OnSelectedItemsPropertyChanged));
private static void OnSelectedItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
var collection = args.NewValue as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged += ((MultiSelectBehavior)target).ContextSelectedItems_CollectionChanged;
}
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItems.CollectionChanged += GridSelectedItems_CollectionChanged;
}
void ContextSelectedItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UnsubscribeFromEvents();
Transfer(SelectedItems as IList, AssociatedObject.SelectedItems);
SubscribeToEvents();
}
void GridSelectedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UnsubscribeFromEvents();
Transfer(AssociatedObject.SelectedItems, SelectedItems as IList);
SubscribeToEvents();
}
private void SubscribeToEvents()
{
AssociatedObject.SelectedItems.CollectionChanged += GridSelectedItems_CollectionChanged;
if (SelectedItems != null)
{
SelectedItems.CollectionChanged += ContextSelectedItems_CollectionChanged;
}
}
private void UnsubscribeFromEvents()
{
AssociatedObject.SelectedItems.CollectionChanged -= GridSelectedItems_CollectionChanged;
if (SelectedItems != null)
{
SelectedItems.CollectionChanged -= ContextSelectedItems_CollectionChanged;
}
}
public static void Transfer(IList source, IList target)
{
if (source == null || target == null)
return;
target.Clear();
foreach (var o in source)
{
target.Add(o);
}
}
}
This behavior takes care of the synchronization between collection RadGridView.SelectedItems and MultiSelectBehavior.SelectedItems.
Now we need to have an ObservableCollection in the ViewModel
//Collection holding the selected items
private ObservableCollection<object> selectedGridItems;
public ObservableCollection<object> SelectedGridItems
{
get
{
if (selectedGridItems == null)
selectedGridItems = new ObservableCollection<object>();
return selectedGridItems;
}
set
{
if (selectedGridItems == value) return;
selectedGridItems = value;
NotifyOfPropertyChange(() => SelectedGridItems);
}
}
//Deselect all selected items in the gridview
public void ClearSelectedGridItems()
{
SelectedGridItems.Clear();
}
Last thing is bind the behavior in the view
<telerik:RadGridView x:Name="CustomLogs" AutoGenerateColumns="true" SelectionMode="Extended">
<i:Interaction.Behaviors>
<local:MultiSelectBehavior SelectedItems="{Binding SelectedGridItems}"/>
</i:Interaction.Behaviors>
</telerik:RadGridView>
Thats it, hope it helps you!

Resources