How do I implement a static variable in viewmodel in Xamarin - xamarin

I have a variable in App.cs:
public static string StatusText = "";
And ViewModel like this:
public string StatusText
{
get { return App.StatusText; }
set
{
if (value == App.StatusText) return;
App.StatusText = value;
OnPropertyChanged();
}
}
In the view I have a label like this:
<Label x:Name="TxtReport" Text="{Binding StatusText}" />
My intent is to have some sort of handle from the View to change StatusText programmatically so any Label bound to it will automatically update.
Also I don't want to assign TxtReport.Text directly because I can only do this safely from the main thread (or use BeginInvokeOnMainThread).
What's the best approach? I have seen suggestions of using MessagingCenter. Is this the only way?

Related

How to get Label view in ViewModel to set accessibility focus in xamarin forms

I have Label in view, I need that Label's view in my ViewModel. I am using Dependency Service to set focus on Controls for Accessibility service, DS requires view as a param.
This is my Label
<Label
AutomationProperties.IsInAccessibleTree="{Binding ShowNoResults}"
IsVisible="{Binding ShowNoResults}"
Text="{Binding ResultsHeader}"/>
I tried Command but Label doesn't support command. Below code also not working
var view = GetView() as HomeworkView;
I am getting view always null. How can I fix this?
I am not quite sure what are you trying to achieve, but you can't access the View elements from you view model.
If you want to do something with the control, you can use the messaging center to do it, here is an example
in your ViewModel
MessagingCenter.Send(this, "your message here");
then in your page, you need to subscribe to this message from that view model and do the desired action
MessagingCenter.Instance.Unsubscribe<ViewModelClassNamedel>(this, "your message here");
MessagingCenter.Instance.Subscribe<ViewModelClassName>(this, "your message here", (data) =>
{
this.youControlName.Focus();
});
More detail added to Mohammad's answer.
Message Center doc.
In your ViewModel (with class name "YourViewModel"):
// Here we use control name directly.
// OR could make an "enum" with a value for each control.
string controlName = ...;
MessagingCenter.Send<YourViewModel>(this, "focus", controlName);
then in your page, subscribe to this message and do the desired action
.xaml.cs:
protected override void OnAppearing() {
{
base.OnAppearing();
// Unsubscribe before Subscribe ensures you don't Subscribe twice, if the page is shown again.
MessagingCenter.Instance.Unsubscribe<YourViewModel>(this, "focus");
MessagingCenter.Instance.Subscribe<YourViewModel>(this, "focus", (controlName) =>
{
View v = null;
switch (controlName) {
case "name1":
v = this.name1;
break;
case "name2":
v = this.name2;
break;
}
if (v != null) {
//v.Focus();
// Tell VM to use v as view.
((YourViewModel)BindingContext).SetFocus(v);
}
});
}
protected override void OnDisappearing() {
MessagingCenter.Instance.Unsubscribe<YourViewModel>(this, "focus");
base.OnDisappearing();
}
If need to pass View v back to VM, because that has the logic to use it:
public class YourViewModel
{
public void SetFocus(View view)
{
... your code that needs label's view ...
}
}
Not tested. Might need some slight changes. Might need
...(this, "focus", (sender, controlName) =>
instead of
...(this, "focus", (controlName) =>
UPDATE
Simple approach, if there is only ONE View that is needed in VM.
public class YourViewModel
{
public View ViewToFocus { get; set; }
// The method that needs ViewToFocus.
void SomeMethod()
{
...
if (ViewToFocus != null)
... do something with it ...
}
}
public class YourView
{
public YourView()
{
InitializeComponent();
...
// After BindingContext is set.
((YourViewModel)BindingContext).ViewToFocus = this.yourLabelThatShouldBeFocused;
}
}
ALTERNATIVE: It might be cleaner/more robust to set ViewToFocus in page's OnAppearing, and clear it in OnDisappearing. This ensures it is never used while the page is not visible (or in some delayed action after the page has gone away). I would probably do it this way.
protected override void OnAppearing()
{
base.OnAppearing();
((YourViewModel)BindingContext).ViewToFocus = this.yourLabelThatShouldBeFocused;
}
protected override void OnDisappearing()
{
((YourViewModel)BindingContext).ViewToFocus = null;
base.OnDisappearing();
}

Why doesn't my Xamarin.Forms Entry get empty in the UI when I set the bound string to ""?

I use the following method to set my variables:
private string _password = "";
public string Password
{
get => _password;
set {
_password = value;
this.OnPropertyChanged("Password");
this.OnPropertyChanged("_password");
}
}
This is within my ViewModel that I bind via:
<ContentPage.BindingContext>
<vm:MainPageViewModel/>
</ContentPage.BindingContext>
I have a Entry in my xaml with:
<Entry Text="{Binding Password,Mode=TwoWay}"
HorizontalOptions="Start"
VerticalOptions="Start">
</Entry>
I have no problem accessing the information in the entry within my ViewModel however when I call
Password = "";
The text of the Entry that gets shown within the UI on the screen stays the same. How do I erase the text within the Entry from my ViewModel?
The behavior happens in Android/iOS and UWP.
Do the following changes and it should work for you :
remove this this.OnPropertyChanged("_password"); as you notify on property changed and not on the field changed.
Hence it would look like:
public string Password
{
get => _password;
set {
_password = value;
this.OnPropertyChanged("Password");
}
}
Secondly, In my knowledge, you might want to do the Password change on MainThread something like:
Device.BeginInvokeOnMainThread(() => { Password = string.Empty; });
The reason being INotifyPropertyChanged is not thread safe! and you could be doing it in an async method

How do I set the name of a Label in Xamarin Forms

I'm trying to add a name to a label so I can find it using the Content.FindByName<>() method
as far as I can tell the Xamarin.Forms.Label() class doesn't have a Name property
Here is my page class
public class HelloWordCodePage : ContentPage
{
public Label HelloWorldLabel;
public HelloWordCodePage()
{
HelloWorldLabel = new Label{Text = "Hello World", };
ConstructView();
}
public void ConstructView()
{
Content = new StackLayout
{
Children =
{
HelloWorldLabel
}
};
}
}
If you define your label in Xaml, you can use this syntex:
<Label x:Name="YourLableName" Text="A simple Label" />
And then access it in the code behind like this
YourLableName.Text = "bla bla"
However, if you don't use Xaml, and all your page definition is found in the code behind, you should keep a reference to that label in order to access it instead of finding it using Content.FindByName<>() as #Sten Petrov commented
I believe the name is only meant to be used when creating the element in XAML. If you look at the inheritance you will find Element at the bottom and it implements INameScope explicitly. You cannot cast Label to INameScope as that is an internal interface:
namespace Xamarin.Forms
{
internal interface INameScope
{
object FindByName(string name);
void RegisterName(string name, object scopedElement);
void UnregisterName(string name);
}
}
The Name is assigned at the Render processing stage. There is no need to know the rendered name at this time as you are working on a cross-platform ContentPage.
There is a GUID, but like you say there is no ID. You wouldn't in my opinion ever want to get a platform-specific ID for the generated control at this high a level of page design. If you do then perhaps you need to create a platform-specific PageRenderer instead?
When you start to write CustomRenderers and create platform specific controls you can then assign IDs if you so wish any custom control platform specific implementations you want.
Remember on the current ContentPage - you have a class-scoped object reference already to your label named HelloWorldLabel, that you can use to change its appearance etc on the page when in execution.
StyleId
Use the StyleId property to identify individual elements in your application
for identification in ui testing and in theme engines.
As IdoT says, if you define an object at runtime, you need to keep a reference to that in order to access it as it is not possible to assign a name.
I use a dictionary like this:
Dictionary<Guid, String> ControlsDictionary = new Dictionary<Guid, string>();
Then when I create an object:
Button bt = new Button {Text="Yay!"};
bt.Clicked += OnButtonClicked;
StackLayout.Children.Add(bt);
ControlsDictionary.Add(bt.Id, "myname");
In the Click event handler I can identify the sender "name":
async void OnButtonClicked(object sender, EventArgs args)
{
Button btn = (Button)sender;
String NameofButton = ControlsDictionary[btn.Id];
}
also you can obtain the Id from the "name":
Guid IdfromName = ControlsDictionary.First(x => x.Value == "myname").Key;
It is bit late but was working through some of xamarin forms issues and remebered I did some thing similar in past, before I started implimenting MVVM. This is how I used Content.FindByName<>() is a rough exmple below.
XAML
<BoxView Color="Gray" HeightRequest="2" HorizontalOptions="Fill" />
<Label x:Name="DoorMaterial" x:Uid="41" Text="Door Material"/>
<RadioButton GroupName="Door_Material" x:Name="RB_99" Value="99" ClassId="41" CheckedChanged="RB_CheckedChanged" Content="Wood" ></RadioButton>
<RadioButton GroupName="Material" x:Name="RB_100" Value="100" ClassId="41" CheckedChanged="RB_CheckedChanged" Content="Steel" ></RadioButton>
<RadioButton GroupName="Material" x:Name="RB_101" Value="101" ClassId="41" CheckedChanged="RB_CheckedChanged" Content="Upvc" ></RadioButton>
<RadioButton GroupName="Material" x:Name="RB_102" Value="102" ClassId="41" CheckedChanged="RB_CheckedChanged" Content="Composite" ></RadioButton>
C#
private void SetPageFields(ICollection<DoorTemplateAnswerRecordModel> answerRecordModelsList)
{
string RadioButtonName = "RB_";
foreach (var answerRecordModel in answerRecordModelsList)
{
var answerId = answerRecordModel.AnswerId.ToString();
var combinedWihtId = RadioButtonName + answerId;
var getCheckBox = Content.FindByName<RadioButton>(combinedWihtId);
if (getCheckBox != null)
{
getCheckBox.IsChecked = true;
}
}
}

How to pass textBlock control to a Class

say I have a textBlock control and I want to pass it to a class which controls the textBlock to display certain Message.
1) When I call a method in the class, I want textBlock to show message. Example " Checking connection...."
2) When the method complete the required task, the textBlock visibility become collapsed.
In the XAML : I have
a) textBlock name=textBlockMsg
b) a Button to call the class
Appreciate your help.
-- Update :
This class file inside project
public class GeoCalculation
{
public GeoCalculation() { }
public void CalculateDistance()
{
//- Begin -- want the textBlockMsg show : in progress......
--code
//-- when end-----, textBlockMsg visibility becom collapse
}
}
If you named you TextBox in the XAML with textBlockMsg, this will work
Edit
// I will not implement the whole INotifyPropertyChanged check how to do to it : implement
public class CalculationClass : INotifyPropertyChanged
{
public void CalculateDistance()
{
TextToBeBound = "in progress..."
--code
VisibilityToBeBound = Collapsed;
}
public string TextToBeBound
{ //... insert the implement of this property + NotifyPropertyChanged
get {...}
set {...}
}
public Visibility VisibilityToBeBound
{ //... insert the implement of this property + NotifyPropertyChanged
get {...}
set {...}
}
}
Then in the XAML add this :
<TextBlock x:Name="txtBlocMsg" Visibility={"Binding VisibilityToBeBound"} Text={Binding TextToBeBound"}/>
Don't forget to set the DataContext of the UI to your class (in my case CalculationClass
You should be good to go. If all this was new. I recommend you read about data Binding + MVVM pattern.
Edit
It's bad practice to pass UI element to model/business classes. You should use the MVVM pattern.
Hope this helps.
You can have a parameter to pass the TextBock:
public void CalculateDistance(TextBlock tb)
{
tb.Text = "in progress..."
--code
tb.Visibility = Visibility.Collapsed;
}
You coud use a the constructor of your class to inject the textblock it should handle
public class GeoCalculation
{
private TextBlock _tb;
public GeoCalculation(TextBlock tb)
{
_tb = tb;
}
public void CalculateDistance()
{
_tb.Text = "in progress..."
//code
_tb.Visibility = Visibility.Collapsed;
}
}
A ViewModel and using DataBinding would be better by the way!
There you could use our class (method) to provice the text for the ui (textbox)
But be aware:
There is a .net way to do this. The GeoCoordinate class contains a method "GetDistanceTo" to calculate the distance between two geo points. See http://msdn.microsoft.com/en-us/library/system.device.location.geocoordinate.getdistanceto.aspx .

validation fails wpf mvvm

learning wpf with mvvm (using EF as ORM).
In my view model i have the property:
//---------------ClientNew
public const string ClientNewConst = "ClientNew";
private TBL_CLIENT _clientNew = new TBL_CLIENT();
public TBL_CLIENT ClientNew
{
get
{
return _clientNew;
}
set
{
if (_clientNew == value)
{
return;
}
var oldValue = _clientNew;
_clientNew = value;
// Update bindings, no broadcast
RaisePropertyChanged(ClientNewConst);
}
}
where TBL_CLIENT - is an entittyobject that mirrors TBL_CLIENT table in DB
now, in my VIEW i bind bunch of textboxes like this(example only for client's first name):
<TextBox Style="{StaticResource ResourceKey=entryFormTextBox}"
Text="{Binding ClientNew.CLIENT_FIRST_NAME,
ValidatesOnDataErrors=True,
NotifyOnValidationError=true,
ValidatesOnExceptions=True,
UpdateSourceTrigger=LostFocus}"
Grid.Column="1"
Grid.Row="1" />
I tried to use different triggers for updatesource.. still teh validation does not work.
oh, i do have implemented idataerrorinfo interface in my viewmodel (but it never hits it..)
#region IDataErrorInfo Members
string IDataErrorInfo.Error
{
get { throw new NotImplementedException(); }
}
string IDataErrorInfo.this[string columnName]
{
get
{
if (string.IsNullOrEmpty("ClientNew.CLIENT_FIRST_NAME"))
{
return "Client Name is required";
}
return null;
}
}
#endregion
so, question.. how can i implement the simple as possible validation using idataerrorinfo for my case, where i don't have the separate property defined in ModelView for each of the entity object, but the property takes the whole entity object?
thanks in advance,
Alex
You might have a look at the BookLibrary sample application of the WPF Application Framework (WAF). It defines the validation rules directly at the entity. Please have a look at "BookLibrary.Domain / Book.cs".

Resources