Binding not working in Rg.Plugins.Popup for custom control - xamarin.forms

I have a custom control based on ContentView with some BindableProperty fields. These controls work great on normal ContentPage pages.
I am now trying to use them in a popup, using the Rg.Plugins.Popup nuget package. The controls display normally, but they never show any values - the binding does not appear to be working. The fields do appear to get bound to null before the constructor of the popups ContentView is called, but nothing happens when the BindingContext is changed.
Here is how the BindableProperty(s) are set up in the control:
public string FieldValue { get; set; }
public static readonly BindableProperty FieldValueProperty = BindableProperty.Create(
propertyName: "FieldValue",
returnType: typeof(string),
declaringType: typeof(MFGField),
defaultValue: "",
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: FieldValuePropertyChanged);
private static void FieldValuePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (MFGField)bindable;
if(newValue != null)
control.ValueEntry.Text = newValue.ToString();
}
Here is how it is set up in the XAML:
<MyNamespace:MFGField x:Name="OrderNumber" FieldValue="{Binding CurrentPick[Order_Number]}" LabelText="Order Number:" ReadOnly="true" Grid.Column="0" Grid.Row="0"/>
The popup ContentView currently has a ViewLifecycleEffect that serves as the 'OnAppearing' for the popup, and that is currently where I am setting the BindingContext after loading the data:
private async void ViewLifecycleEffect_OnLoaded(object sender, EventArgs e)
{
await callingPage.LoadBinRecord(callingPage.SearchTextValue);
((AppData)this.BindingContext).CurrentPick = ((AppData)this.BindingContext).CurrentPicks[((AppData)this.BindingContext).PickIndex];
}
I have confirmed that the data in the object that should be bound is present and correct, but the PropertyChanged functions for my fields don't get called when the BindingContext is updated.
I am coming back to Xamarin Forms after some time, and I'm sure I'm missing something obvious, but I am missing it nevertheless. Any help is appreciated.

My apologies, this issue appears to be the result of my unfortunate choice of fields to which to bind. There are well over 200 fields in the object, and I happened to select several quite reasonable choices for testing, where the values are unexpectedly null.
Initially, I did have a problem getting the BindingContext set properly, and the field issue prevented me from seeing that I had actually fixed that problem. The binding does in fact seem to be working now that the correct fields are bound.

Related

MVVMCross How to get ViewModel Instance within View Code Behind

I'm using MVVMCross 7.1.2 and have a situation where several of my pages can't inherit the MvxContentPage class. Understandably this breaks a few things that MVVMCross implements.
One thing I noticed is the BindingContext for the page does not get set and as a result we get a NullReference exception which is difficult to debug.
What is the best was to access the ViewModel Instance form the Views code behind ? At the moment I'm using the interface IMvxOverridePresentationAttribute and then implementing it like this:
public MvxBasePresentationAttribute PresentationAttribute(MvxViewModelRequest request)
{
BindingContext = ((MvxViewModelInstanceRequest) request).ViewModelInstance;
InitializeComponent(); <--- Update 1,moved from ctor
return null;
}
Is this the best way to get the VM instance ? or is there a better to the BindingContext automatically set.
UPDATE 1;
I still get the NullReference Exception with this method presumably as it sets the BindingContext after InitializeComponent is called. I've tried moving the InitializeComponent call to after the BindingContext is set but the page doesn't render correctly.
Have you tried something like this?
public partial class SomeView : ContenPage
{
public ViewModels.NestedViewModel ViewModel {get; set;}
public SomeView()
{
InitializeComponent();
// when viewmodel already created
if (Mvx.IoCProvider.TryResolve<ViewModels.NestedViewModel>(out var someViewModel))
{
ViewModel = someViewModel;
BindingContext = ViewModel;
return;
}
// creating viewmodel
var _viewModelLoader = Mvx.IoCProvider.Resolve<IMvxViewModelLoader>();
var request = new MvxViewModelInstanceRequest(typeof(ViewModels.NestedViewModel));
request.ViewModelInstance = _viewModelLoader.LoadViewModel(request, null);
ViewModel = request.ViewModelInstance as ViewModels.NestedViewModel;
BindingContext = ViewModel;
Mvx.IoCProvider.RegisterSingleton<ViewModels.NestedViewModel>(ViewModel);
}
}

Update OnPropertyChanged on a Parent View from a child view

In Xamarin for mac, I decided to make multiple views to be used within my main view using the MVVM pattern.
The thing is that I have a ListView within my MainPage which pulls a List of items from a model, and the list is populated within a child view, with its own ViewModel.
When I add a new service from the child view, I would like for the OnPropertyChanged event on the parent view model to trigger.
It is working by navigating to the parent view and setting the animation to false, but this is not really nice looking. It worked though when I had all code within one ViewModel.
How I tried to achieve this, and the errors I got:
0 - Accessing the command within the child model from the parent model, and passing the propertychanged event handler along.
I Couldn't do it. I tried this by making a bindable command like below, but this is not doable for me as I don't think it is possible for the command to know when the property will be changed, which is the whole point of this problem.
If it is doable, I don't know how.
//public static readonly BindableProperty SaveServiceClickedCommandProperty =
// BindableProperty.Create(
// "SaveServiceClicked",
// typeof(Command),
// typeof(NewServiceViewModel),
// null);
1 - Passing the parent view model on the child view model, and put a OnPropertyChanged(nameof(parentModel.List)) at the clicked event handler.
public class ChildViewModel : INotifyPropertyChanged
{
public ICommand AddEntryClickedCommand { get; private set; }
private MainModel mainModel;
// property changed handler
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public NewServiceViewModel()
{
Navigation = MainPage;
//async void execute() => await OpenPage();
//OpenPageCommand = new Command(execute, () => !IsBusy);
//async Task OpenPage()
//{
// await Navigation.PushAsync(new MainPage());
//}
// Here I tried to access the data from within the main model.
mainModel = new MainModel(Navigation);
InitMainModel();
void InitMainModel()
{
MainPage mainView = new MainPage();
mainView.BindingContext = mainModel;
}
async void c1() => await AddEntryClicked();
AddEntryClickedCommand = new Command(c1);
}
public async Task<bool> AddEntryClicked()
{
OnPropertyChanged(nameof(mainModel.List))
}
The attempt above created some errors as the object is already populated.
Leading to me thinking that I don't have the right approach altogether.
My solution being to re-introduce the child view within the parent view, and change IsVisible according to the button being clicked or not, as I already did with other smaller component.
I have thought about pulling the list from the child view, but that's raises the same issue of non-null collection.
Of course, the code has been modified to show only the gist.
Thanks in advance.

Binding a URL to custom SVG container's BindableProperty

I have been using the code layout here to create an SVG container for a Xamarin Forms project. It works well and I reconfigured it to read the image from a remote URL.
The ResourceId is from the source above:
public static readonly BindableProperty SvgUriProperty = BindableProperty.Create(
"ResourceId",
typeof(string),
typeof(SvgUriContainer),
default(string),
propertyChanged: RedrawCanvas);
public string ResourceId
{
get => (string)GetValue(SvgUriProperty);
set => SetValue(SvgUriProperty, value);
}
However I can not seem to bind that URL at run time in the XAML:
<control:SvgUriContainer
Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="0"
ResourceId="{Binding StampUri}"
[...]
/>
That Binding returns a string and the rest of the bindings work fine. Any attempt to bind in that fashion results in a build error:
No property, bindable property, or event found for 'ResourceId', or mismatching type between value and property.
No matter what I do in the container logic with the BindablePropertycreation, the error is the same and is in the XAML. Am I getting the syntax wrong in the XAML? Is it because Binding is a BindingExtension (not a string) at build time?
Note: if I replace the Binding with a string/URL it all works fine.
Note: if I set the type of ResourceId to object I can get by the build error but the string does not resolve, of course.
Can you try to modify your bindableProperty to the standard format described in document:
public static readonly BindableProperty SvgUriProperty = BindableProperty.Create(
"SvgUri",
typeof(string),
typeof(EventToCommandBehavior),
"test",
propertyChanged: RedrawCanvas);
public string SvgUri
{
get => (string)GetValue(SvgUriProperty);
set => SetValue(SvgUriProperty, value);
}
Xamarin requires a Property naming convention.

Notifying dependency property change to its observers

I have the following in xaml.
<TextBlock Text="{Binding Title}" />
And created the following dependency property,
public string Title
{
get { return (string)GetValue(TitleProperty); }
set
{
SetValue(TitleProperty, value);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Title"));
}
}
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(ColumnChart), new PropertyMetadata(string.Empty));
Now if I bind the Title property in other xaml, the value is not taken. Because the PropertyChange notification is not called. And always PropertyChanged is null.
How I can notify to the list of observers that this property is changed, so that the value will be updated.
I'm not quite sure what you mean by "How I can notify to the list of observers that this property is changed, so that the value will be updated."
This looks like a user control, as it's not typical to use dependency properties in view models. Therefore, have a look at Routed Events. The Register() method for a dependency property has an override which will take a handler which will be invoked when the property changes. You can invoke a custom routed event from within this handler. Consumers of your user control can subscribe to this routed event using the standard mechanisms.

Caliburn Micro: querying view-specific data from the VM

I'm completely new to CM and also to learn it I'm migrating an application from MVVM light to Caliburn Micro. In my original code, I had a VM which responds to some UI actions (via commands) to replace some text into a string. The position is given by the view, using the textbox selection.
So the VM has (1) a bound string property representing the textbox's text, (2) another bound string property to represent the new text to be added, and (3) needs to know selection start and length in order to replace the right portion of text with the new one.
In my original code, I had a custom DialogMessage-derived object sent in the VM command implementation with a couple of properties for selection data: when the command was issued, the message was sent, and the view received it and filled it with its textbox selection start and length; then the VM was called back and could use these data.
Which would be the best way of implementing this in CM? I'd prefer the VM to remain agnostic of the view, so I don't like too much the idea of accessing the view from it. I'd rather opt for a "message"-based mechanism like the above, but I'm not sure how I can implement it in CM: I would probably look at IResult, but most of the samples I find are related to coroutines and I'm not sure how to relate the void ReplaceText() method of the VM to the view code behind.
Could anyone point me in the right direction, and/or to some code samples about dialog-like interactions between VM 'command' methods and view? Thanks!
I'd probably look at the IResult option. You'll have access to the view so code that you would have had in the code behind can be in your Result and not in your VM.
Here is code from a ShowDialog result. I believe I grabbed it from the CM discussion group. Search the discussion group for ShowDialog for more examples. The GameLibrary sample that comes with CM also has some.
public class ShowDialog : IResult
{
private readonly Type _screenType;
private readonly string _name;
[Import]
public IWindowManager WindowManager { get; set; }
public ShowDialog(string name)
{
_name = name;
}
public ShowDialog(Type screenType)
{
_screenType = screenType;
}
public void Execute(ActionExecutionContext context)
{
var screen = !string.IsNullOrEmpty(_name)
? IoC.Get<object>(_name)
: IoC.GetInstance(_screenType, null);
Dialog = screen;
WindowManager.ShowDialog(screen);
var deactivated = screen as IDeactivate;
if (deactivated == null)
Completed(this, new ResultCompletionEventArgs());
else
{
deactivated.Deactivated += (o, e) =>
{
if (e.WasClosed)
{
Completed(this, new ResultCompletionEventArgs());
}
};
}
}
public object Dialog { get; private set; }
public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
public static ShowDialog Of<T>()
{
return new ShowDialog(typeof (T));
}
}
edit: If you extend TextBox you can bind SelectedText.
public class TextBoxEx : TextBox
{
public static readonly DependencyProperty SelectedTextProperty = DependencyProperty.Register("SelectedText", typeof(string), typeof(TextBoxEx), new PropertyMetadata("oa"));
public TextBoxEx()
{
SelectionChanged += UpdateDependencyProperty;
}
private void UpdateDependencyProperty(object sender, RoutedEventArgs e)
{
SelectedText = base.SelectedText;
}
public new string SelectedText
{
get { return GetValue(SelectedTextProperty).ToString(); }
set { SetValue(SelectedTextProperty, base.SelectedText); }
}
}
then:
<SLTest:TextBoxEx x:Name="MyTextBox2"
Grid.Row="1"
Width="200"
SelectedText="{Binding SelectedText, Mode=TwoWay}"
Text="This is some text." />

Resources