I am trying to set source to my WebView in my ViewModel, however it resulted in invalid cast exception. I have tried to set the source directly In xml and it worked so I must be doing something wrong in the viewModel. Could you please advise
My ViewModel
public WebViewSource PageSource
{
get => (string) GetValue(_pageSource);
set => SetValue(_pageSource, value);
}
private readonly BindableProperty _pageSource = BindableProperty.Create(
propertyName: nameof(PageSource),
returnType: typeof(WebViewSource),
defaultValue: default(WebViewSource),
declaringType: typeof(WebBrowserViewModel));
And then I have this in the constructor
public WebBrowserViewModel()
{
WebViewSource source = new UrlWebViewSource
{
Url = "https://google.com"
};
PageSource = source;
}
my xaml
<page:Page.BindingContext>
<viewmodel:WebBrowserViewModel />
</page:Page.BindingContext>
<AbsoluteLayout>
<WebView
Source="{Binding PageSource}"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0,0,1,1" />
</AbsoluteLayout>
</page:Page>
You specify the incorrect type in getter method .
Change your code from
public WebViewSource PageSource
{
get => (string)GetValue(_pageSource);
set => SetValue(_pageSource, value);
}
To
public WebViewSource PageSource
{
get => (WebViewSource)GetValue(_pageSource);
set => SetValue(_pageSource, value);
}
Related
I have entry fields in a Xamarin Forms page that I want to trigger a ReactiveUI command when the user is finished entering text into them. I am using ReactiveUI.Events.XamForms and am trying to trigger a command based off of the Unfocused event, but I am not sure how to set up the command to get that to work.
Here is my XAML:
<?xml version="1.0" encoding="utf-8" ?>
<rxui:ReactiveContentPage
x:Class="XamarinReactiveUISwipeView.MainPage"
x:TypeArguments="vm:MainPageViewModel"
xmlns:vm="clr-namespace:XamarinReactiveUITest.ViewModel;assembly=XamarinReactiveUITest"
xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr- namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
xmlns="http://xamarin.com/schemas/2014/forms"
ios:Page.UseSafeArea="true">
<StackLayout>
<StackLayout Margin="10,0,0,0" Orientation="Horizontal">
<Label Text="Task ID: " />
<Entry x:Name="EntryTaskID" />
</StackLayout>
<StackLayout Margin="10,0,0,0" Orientation="Horizontal">
<Label Text="Task Name: " />
<Entry x:Name="EntryTaskName" />
</StackLayout>
</StackLayout>
</rxui:ReactiveContentPage>
Here is my code behind:
public partial class MainPage : ReactiveContentPage<MainPageViewModel>
{
public MainPage()
{
InitializeComponent();
ViewModel = new MainPageViewModel();
this.WhenActivated(disposable =>
{
this.Bind(ViewModel, vm => vm.TheTaskItem.TaskID, page => page.EntryTaskID.Text)
.DisposeWith(disposable);
this.Bind(ViewModel, vm => vm.TheTaskItem.TaskName, page => page.EntryTaskName.Text)
.DisposeWith(disposable);
EntryTaskName.Events().Unfocused.InvokeCommand(ViewModel, vm => vm.TheCommand);
});
}
}
here is my model:
public class TaskItem
{
public TaskItem() { }
public string TaskID { get; set; }
public string TaskName { get; set; }
}
and here is my view model:
public class MainPageViewModel : ReactiveObject
{
public MainPageViewModel()
{
TheTaskItem = new TaskItem { TaskID = "1", TaskName = "TheTaskName" };
TheCommand = ReactiveCommand.Create<FocusEventArgs, Unit>(ExecuteTheCommand);
}
public ReactiveCommand<FocusEventArgs, Unit> TheCommand { get; }
private void ExecuteTheCommand(FocusEventArgs args)
{
//do something
}
private TaskItem _theTaskItem;
public TaskItem TheTaskItem
{
get => _theTaskItem;
set => this.RaiseAndSetIfChanged(ref _theTaskItem, value);
}
}
In the view model above, it won't compile, but I can't figure out how to set up the ExecuteTheCommand method. The error is:
'void MainPageViewModel.ExecuteTheCommand(FocusEventArgs)' has the wrong return type
But in looking at examples, it looked like methods with void returns use the Unit type.
What do I need to do here to set up the command properly to get this to work?
In comment above, OP says that this works if change:
TheCommand = ReactiveCommand.Create<FocusEventArgs, Unit>(ExecuteTheCommand);
to:
TheCommand = ReactiveCommand.Create<FocusEventArgs>(ExecuteTheCommand);
Unexpectedly, the type of the variable that holds the result of Create, does not use that same generic type signature. It needs to be:
public ReactiveCommand<FocusEventArgs, Unit> TheCommand { get; }
According to the doc in link by Rodney Littles in comment, Unit is used to represent "void return type". (Generic type "TOutput" would be "void"; but "void" is not a valid Generic type.)
I have a DateTimeViewModel with DateTime property of type DateTime.
I can do actually in XAML as follows. And it works.
<StackLayout.BindingContext>
<Binding Path="DateTime">
<Binding.Source>
<toolkit:DateTimeViewModel/>
</Binding.Source>
</Binding>
</StackLayout.BindingContext>
<Label Text="{Binding Hour,StringFormat='The hours are {0}'}" />
<Label Text="{Binding Minute,StringFormat='The minutes are {0}'}" />
<Label Text="{Binding Second,StringFormat='The seconds are {0}'}" />
<Label Text="{Binding Millisecond,StringFormat='The milliseconds are {0}'}" />
However I am interested in learning whether I can remove
<StackLayout.BindingContext>
<Binding Path="DateTime">
<Binding.Source>
<toolkit:DateTimeViewModel/>
</Binding.Source>
</Binding>
</StackLayout.BindingContext>
and replace it with something like as follows (not works anyway)
BindingContext = new DateTimeViewModel();
this.SetBinding(Label.TextProperty, "DateTime");
Could you tell me how to set the binding path programmatically?
Edit
To make less confusing, let me add the details here.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class DateTimeViewModel : ViewModelBase
{
public DateTimeViewModel()
{
Device.StartTimer(TimeSpan.FromMilliseconds(15),
() =>
{
DateTime = DateTime.Now;
Counter++;
return true;
}
);
}
private DateTime dateTime = DateTime.Now;
public DateTime DateTime
{
get => dateTime;
private set => SetProperty(ref dateTime, value);
}
private long counter = 0;
public long Counter { get => counter; private set => SetProperty(ref counter, value); }
}
To set the binding path programmatically you have to use the following code:
BindableObject.SetBinding(BindableProperty targetProperty, new Binding() {Path = "path"})
In your case it would be something like the following:
this.SetBinding(Label.TextProperty, new Binding() {Path = "DateTime"});
You should bind properties of the same type, in your code you are trying to bind a Label.TextProperty with a property of the type DateTime. make the DateTime property in your ViewModel one of the type "string", and then do the conversion in the ViewModel to "DateTime" or vice versa.
You should not also set parameters, variables, or functions names equal to their type.
How do I pass property values to custom control children without writing code?
Content page:
<views:ButtonView Image="newViolationSmall.png" Text="{Binding ParentsText}/>
Then ButtonView.xaml
<StackLayout
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SmartwebsCrossPlatform.Portable.Views.ButtonView">
<Button Image="{Binding Image}"/>
<Label Text="{Binding Text}" />
</StackLayout>
And ButtonView.cs
public partial class ButtonView : StackLayout {
...
//text
public static readonly BindableProperty TextProperty = BindableProperty.Create( "Text", typeof( string ), typeof( ButtonView ), null, BindingMode.Default, null, null, null, null );
public string Text {
get {
return (string)GetValue( TextProperty );
}
set {
SetValue( TextProperty, value );
}
}
//image
public static readonly BindableProperty ImageProperty = BindableProperty.Create( "Image", typeof( string ), typeof( ButtonView ) );
public string Image {
get {
return (string)GetValue( ImageProperty );
}
set {
SetValue( ImageProperty, value );
}
}
This does not show any text or image. A rather annoying alternative is to name both Button and Label, override ButtonView.OnPropertyChanged, and explicitly set their properties from code but it causes a System.ObjectDisposedException (Object name: 'Android.Graphics.Bitmap') when I navigate around this page.
#yyou's solution works for const values, but not for parent's properties.
The only thing missing is to set the BindingContext of this custom layout.
ie.
//ButtonView.xaml.cs
public ButtonView ()
{
InitializeComponent ();
BindingContext = this;
}
//to use this ButtonView component in a page
//ie. DetailPage.xaml
<ContentPage ...>
<app2:ButtonView Text="{Binding ParentText}" Image="{Binding ButtonImage }"/>
</ContentPage>
//in DetailPage.xaml.cs
public DetailPage() {
InitializeComponent ();
BindingContext = new DetailViewModel() {
ParentText = "sjldfdkfdjd",
ButtonImage = "my-button-image.png"
};
}
//in the file DetailViewModel.cs
namespace App2.ViewModels
{
public class DetailViewModel : BaseViewModel {
private string _parentText;
public string ParentText {
get { return _parentText; }
set { SetProperty<string>(ref _parentText, value); }
}
private string _buttonImage;
public string ButtonImage {
get { return _buttonImage; }
set { SetProperty<string>(ref _buttonImage, value); }
}
//other stuff
}
}
My view...
public partial class TableRow : ViewCell
{
public static readonly BindableProperty TextProperty =
BindableProperty.Create("Text", typeof(string), typeof(TableRow), null);
public string Text
{
get => (string)GetValue(TextProperty);
set
{
SetValue(TextProperty, value);
lblValue.Text = value;
}
}
Used like this...
<Custom:TableRow NameText="Name" Text="{Binding Name}" />
But when I set the binding context...
BindingContext = candidate;
... you've guessed it! Nothing happens!
What's the missing step?
It's the page's BindingContext that candidate is set to.
Is there an alternative for a Tag Bindable Property?
A tag property is available in WPF. However, it does not exist in Xamarin.Forms.
I would like to create a bindable context between two elements.
Specifically, I would like to bind a label's Tag property to an entry's Text property.
I have attempted the following:
Tag Property
public class TagBehavior
{
public static readonly BindableProperty TagProperty = BindableProperty.CreateAttached<TagBehavior, BindableObject>(
bindableObject => TagBehavior.GetTag(bindableObject),
null, /* default value */
BindingMode.OneWay,
null,
(bo, old, #new) => TagBehavior.OnCompletedChanged(bo, old, #new),
null,
null);
public static BindableObject GetTag(BindableObject bindableObject)
{
return (BindableObject)bindableObject.GetValue(TagBehavior.TagProperty);
}
public static void SetTag(BindableObject bindingObject, BindableObject value)
{
bindingObject.SetValue(TagBehavior.TagProperty, value);
}
public static void OnCompletedChanged(BindableObject bindableObject, BindableObject oldValue, BindableObject newValue)
{
//var tag = TagBehavior.GetTag(entry);
//if (tag != null)
//{
// Debug.WriteLine("TODO - Handle tag's value change event");
//}
}
}
XAML
xmlns:Behaviors="clr-namespace:ViewMenu.Behaviors;assembly=ViewMenu"
. . .
<Entry x:Name="textBox1" BindingContext="{StaticResource ViewModel}" Text="{Binding Path=Content1}" Grid.Row="0" Grid.Column="0" >
<Entry.Behaviors>
<Behaviors:TextBoxFocusBehavior />
</Entry.Behaviors>
</Entry>
<Label x:Name="label1" Grid.Row="0" Grid.Column="0"
Behaviors:TagBehavior.Tag="{Binding Source={x:Reference textBox1}, Path=Text}">
<Label.Behaviors>
<Behaviors:LabelDisplayBehavior />
</Label.Behaviors>
</Label>
However, I get an error in the output window saying:
SetValue: Can not convert to type
'Xamarin.Forms.BindableObject'
Any suggestions?
You're creating an attached property of type BindableObject which you try to bind to the property Text of the TextBox which is of type string. Obviously, string cannot be casted to BindableObject hence the error.
Instead, declare your attached property of type string, or if you want to stay more general, of type object:
public class TagBehavior
{
public static readonly BindableProperty TagProperty = BindableProperty.CreateAttached<TagBehavior, string>(
bindableObject => TagBehavior.GetTag(bindableObject),
null, /* default value */
BindingMode.OneWay,
null,
(bo, old, #new) => TagBehavior.OnCompletedChanged(bo, old, #new),
null,
null);
public static string GetTag(BindableObject bindableObject)
{
return (string)bindableObject.GetValue(TagBehavior.TagProperty);
}
public static void SetTag(BindableObject bindingObject, string value)
{
bindingObject.SetValue(TagBehavior.TagProperty, value);
}
public static void OnCompletedChanged(BindableObject bindableObject, string oldValue, string newValue)
{
//var tag = TagBehavior.GetTag(entry);
//if (tag != null)
//{
// Debug.WriteLine("TODO - Handle tag's value change event");
//}
}
}