Xamarin Forms Force view to bind values - xamarin

I have a view. I have a bindable property there.
public partial class OrderCard : ContentView
{
public static readonly BindableProperty OrderProperty = BindableProperty.Create(nameof(Order), typeof(Order), typeof(OrderCard), null);
public Order Order
{
get { return (Order)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
public OrderCard()
{
InitializeComponent();
}
}
In the xaml of this view I'm binding to Order property like this:
Text="{Binding Order.Name, Source={x:Reference Root}}"
Root is a name in the xaml of a view OrderCard
When I use this view in the page everything works ok.
But I want to measure it's size even before adding it to the page.
var orderCard = new OrderCard { Order = order};
SizeRequest sizeRequest = orderCard.Measure(OrdersContainer.Width/5, OrdersContainer.Height);
But it gives me wrong numbers because bindings isn't applied.
How to force to apply bindings when view isn't attached to the page?

Bindings do not require being attached to a Page or anything else to be applied.
You might think they're not applied if your method for figuring out is to set a breakpoint on get_Order because that is never used, the Xaml loader uses GetValue directly. The usual way of figuring out if a Binding is correctly applied, is to add a PassthroughConverter (don't look for it, you have to write it yourself) to the Binding and put the breakpoint in the Convert method.
That being said, you can't Measure anything unless it's added to a page that is rendered on screen. If you try to Measure before that, you indeed get dummy values.

I was able to solve this problem by not doing a property Order but passing an order as BindingContext. Then I can measure the size of a view without attaching it to a page like this:
var orderCard = new OrderCard { BindingContext = order};
SizeRequest sizeRequest = orderCard.Measure(widthToTryToFitInTheView,heightToTryToFitInTheView);

Related

What is the property in control to be set to get the value from AppResult.Text in Xamarin.UITest?

I am trying to create custom control in Xamarin.Forms which has the unique id for automation. So, i have set the android renderer's contentDescription property. So, i can get the AppResult.Label property to identify the control. But, my requirements is that how to get the control's text property? What property i have to set in control level with the corresponding text to get it in AppResult.Text property.
[Test]
[Description("SampleTest")]
public void WelcomeTextIsDisplayed()
{
App.Repl();
AppResult[] results = App.WaitForElement("myControl");
Assert.IsTrue(results[0].Text == "My Control Text", results[0].Text + "\n" + results[0].Description + "\n" + results[0].Id + "\n" + results[0].Label);
}
For more information, I have prepared the simple example to explain better about my case. Here, i have derived my custom control from Grid and i introduced the Text property. When i try to view the element using Repl() method, it does not show the Text property but it shows the text properties for Label & Entry controls.
<StackLayout >
<Label Text="Hello, Custom Renderer!" />
<local:MyEntry Text="In Shared Code" AutomationId="myEntry" />
<local1:CustomView Text="Sample" BackgroundColor="Red" HeightRequest="500" AutomationId="customControl" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"/>
</StackLayout>
public class CustomView : Grid
{
public CustomView()
{
}
public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(string),string.Empty);
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
Result while calling App.Repl() ,
I'm not sure how different Xamarin.Forms are to Xamarin.Android (which is mostly what my experience is in.)
What happens if you try
app.Query(c => c.Id("NoResourceEntry-2").Property("Text")).SingleOrDefault();
or some variation of the above? Can you then do something with this? I Hope this helps or points you in the right direction.
Try to use with index position like this:
app.Query(c=>c.Id("NoResourceEntry-2"))[0].Text
similarly you can use class for same:
app.Query(c=>c.Class("LabelRenderer"))[0].Text
Query for Class("LabelRenderer") gave 2 results. So in above example, you can see it gave you 2 results but you have to use index for particular result.

In a Xamarin MVVM application, how can I change what I see on the screen from the ViewModel?

My application viewModel responds to a user clicking a button to see test results:
private void AddDetailRows(List<QuizHistory> quizHistoryList)
{
quizDetails.Children.Clear();
quizDetails.Children.Add(AddData(quizHistoryList));
quizDetails.Children.Add(new LineTemplate());
}
Where quizDetails is the name of an element in the view.
But this doesn't work for me as the view model doesn't know what the view looks like and does not have access to the names of elements.
In a MVVM application, how is this problem solved?
You are completely right, that is not something that ViewModel is responsible of.
So, whatever you want to do with UI is not responsibility of the ViewModel.
If this is really the only option, then you can think of creating boolean properties in your VM and binding them to your views and then changing that boolean from false to true or vice versa on button click command which is binded to your VM.
To simplify it:
MyView.xaml
<StackLayout>
<Button Command="{Binding ShowHideQuizHistoryCommand}" ... />
<StackLayout x:Name="QuizHistory"
IsVisible={Binding ShowQuizHistory }>
//
</StackLayout>
</StackLayout>
MyViewModel.cs
private bool _showQuizHistory ;
public bool ShowQuizHistory
{
get { return _showQuizHistory ; }
set
{
_showQuizHistory = value;
OnPropertyChanged();
}
}
public ICommand ShowHideQuizHistoryCommand => new Command(() =>
{
ShowQuizHistory = !ShowQuizHistory;
});
So, this is just an example based on what you provided in question.
You can also use visual states, converters, triggers and behaviors in order to achieve this, but in my opinion this is the easiest way.

Why is Xamarin DatePicker Binding causing a navigation failure?

I am using Prism and Autofac with Xamarin.Forms 4.0 with an MVVM architecture. Using the Navigation.NavigateAsync("MyPage") works unless I have a binding to the Date object with my ViewModel.
The page renders properly and I am navigated to it if my DatePicker has no binding.
<DatePicker x:Name="ProcessStartDate" Format="D" MinimumDate="01/01/2000" />
However the following will cause me to never navigate to the page.
<DatePicker x:Name="ProcessStartDate" Format="D" MinimumDate="01/01/2000" Date="{Binding SelectedStartDate, Mode=TwoWay}"
The property in the View Model, MyVM, looks like this.
private DateTime selectedStartDate;
public DateTime SelectedStartDate
{
get
{
return selectedStartDate;
}
set
{
SetProperty(ref selectedStartDate, value);
sample.ProcessStartDate = value;
}
}
Navigation with the following code fails with the Binding in XAML above:
INavigationResult status;
try
{
var parameters = new NavigationParameters();
parameters.Add("CurrentSample", SelectedSample);
status = await NavigationService.NavigateAsync("MyPage", parameters); //MyPage is registered with MyVM
}
catch (Exception ex)
{
string mess = ex.Message;
}
My work-around is to add an event handler to the code-behind.
<DatePicker x:Name="ProcessStartDate" Format="D" MinimumDate="01/01/2000" DateSelected="OnStartDateSelected"
So now my code-behind has a handler:
void OnStartDateSelected(object sender, DateChangedEventArgs args)
{
SampleDetailsViewModel vm = BindingContext as SampleDetailsViewModel;
vm.SelectedStartDate = args.NewDate;
}
I have a work-around for this page, But I don't want put code in the code-behind. This breaks the MVVM standard that I've managed to maintain on the other seven pages of the app. Am I Binding improperly with the DatePicker?
When Binding SelectedStartDate, you are not initializing it, making it binding to a null, because you have set the Binding Mode to "TwoWay".
Here you can find the various types of binding modes, quoting:
Causes changes to either the source property or the target property to
automatically update the other. This type of binding is appropriate
for editable forms or other fully-interactive UI scenarios.
a solution would be something like this (if you wanna keep the TwoWay mode, and dont mind starting with an default selected):
private DateTime selectedStartDate = DateTime.Now;
Or
Making the binding mode to "OneWayToSource", this makes updates to the binding source without, and not the target (remember that this way you can't change the selected date from the binding, only the datepicker can).
Updates the source property when the target property changes.
Or
If you wanna keep the TwoWay Mode and not having a default date selected, the way you did with code behind is a nice workaround.

Xamarin Forms: Multiple items for a converter

I have a custom view which shows a "No Results" message when a page has no values.
<common:NoResults
IsVisible="{Binding Details,
Converter={ StaticResource EmptyListIsTrueConverter }}"></common:NoResults>
The problem is when the page is loading, there are no values so it shows and should not.
Is there a way to combine a check for an empty list and another Model property like IsBusy into one Converter ?
In IsVisible set the binding to a property DoneLoadingAndNoValues in the viewmodel with default value false.
Set this property to true when loading is done and Details contains no values.
ViewModel (implements INotifyPropertyChanged with OnPropertyChanged):
private bool doneLoadingAndNoValues = false; // default is false
public bool DoneLoadingAndNoValues
{
get { return doneLoadingAndNoValues; }
set
{
doneLoadingAndNoValues= value;
OnPropertyChanged(nameof(DoneLoadingAndNoValues));
}
}
...
// Done loading and Details contains no values:
DoneLoadingAndNoValues = true;
XAML:
<common:NoResults IsVisible="{Binding DoneLoadingAndNoValues}" />
I can't see a way to easily do this in XAML. Try it in your code behind.
When the page is finished loading, execute a function which checks the how many values there are. If there are no values, show your no values message.

How do I bind TwoWay between a CheckBox in a UserControl and the ViewModel in the wider scope?

I have a UserControl that has a CheckBox on it. When I consume the UserControl on my main XAML page, I'd like to TwoWay bind a property on the control to a property on my ViewModel e.g.
<myUserControl BtnIsBlacklisted="{Binding IsBlacklisted, Mode=TwoWay}" />
When IsBlacklisted changes, I'd like my checkbox to change too and vice-versa.
Here is what I have,
public static readonly DependencyProperty BtnIsBlacklistedProperty =
DependencyProperty.Register("BtnIsBlacklisted",
typeof(bool),
typeof(MyUserControl),
new PropertyMetadata(false, new
PropertyChangedCallback(BtnIsBlacklistedPropertyChanged))
);
private static void BtnIsBlacklistedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// ... do something here ...
}
public bool BtnIsBlacklisted
{
get { return (bool)GetValue(BtnIsBlacklistedProperty); }
set { SetValue(BtnIsBlacklistedProperty, value); }
}
My UserControl has this for the CheckBox,
<CheckBox x:Name="myCheckBox"
...
IsChecked="{Binding Path=BtnIsBlacklisted,
ElementName=UserControl,
Converter={StaticResource BoolToNotBool},
Mode=TwoWay}" />
The property on my ViewModel object is as follows,
public bool IsBlacklisted
{
get
{
return App.VM.BlacklistedRetailers.Contains(this.Retailer);
}
set
{
if (value)
{
App.VM.BlacklistedRetailers.Add(this.Retailer);
}
else
{
while (App.VM.BlacklistedRetailers.Contains(this.Retailer))
{
App.VM.BlacklistedRetailers.Remove(this.Retailer);
}
}
this.NotifyPropertyChanged("IsBlacklisted");
}
}
The only way BlacklistedRetailers changes is through the set method above so there is no need to trigger a NotifyPropertyChanged from there ...
I have tried many of the suggestions in other questions i.e.
using a dependency property
including Mode=TwoWay
Binding on the UserControl using a self-referencing DataContext set on the containing grid (this does not work either).
however none of these have worked.
Some final notes:
This is for a Windows Phone 7.5 project
Edit: One way binding doe not work either, it seems it there is a problem binding to the UserControl's own properties
An ElementName Binding matches against x:Name values which are in the same name scope as the element on which the binding is being set. There's not enough of the code shown to tell but you're using "UserControl" which I'm guessing is not set as the name of the element, but is being used to try and match the type. The ElementName also might not be able to resolve if the CheckBox is declared inside a template.

Resources