How to bind to a WPF dependency property to window? - data-binding

i create a dependency property to close a view from view model,
dependencyProperty:
public static class WindowBehaviors
{
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.RegisterAttached("IsOpen"
, typeof(bool),
typeof(WindowBehaviors),
new UIPropertyMetadata(false, IsOpenChanged));
private static void IsOpenChanged(DependencyObject obj,DependencyPropertyChangedEventArgs args)
{
Window window = Window.GetWindow(obj);
if (window != null && ((bool)args.NewValue))
window.Close();
}
public static bool GetIsOpen(Window target)
{
return (bool)target.GetValue(IsOpenProperty);
}
public static void SetIsOpen(Window target, bool value)
{
target.SetValue(IsOpenProperty, value);
}
}
and use it in my xaml like this:
<window
...
Command:WindowBehaviors.IsOpen="True">
it work's fine,but when i want to bind it to a property in viewModel,it dosen't work,and i guess,it dosen't work because i define the resource later in xaml.
in xaml:
<Window.Resources>
<VVM:myVieModel x:Key="myVieModel"/>
</Window.Resources>
and i don't know what should i do,where should i put this:
Command:WindowBehaviors.IsOpen="{binding Isopen}"

public MainWindow()
{
InitializeComponent();
// DO THIS
this.DataContext = Resources["myVieModel"];
}

You need to bind the data context for the scope where your binding is in. Usually this is fairly high up in your XAML, usually the first element in your form or control.
In your case, the data context beeing a static resource the folllowing should work:
<grid DataContext="{StaticResource myVieModel}">
<!-- the code with the binding goß into here -->
</grid>
Actually this is the same as ebattulga suggests, just the XAML way (no code behind).

Thanks for your helps,i fixed it and here is my solution,
i used to use MVVMToolkit but now i'm useing MVVMlight and as you know in MVVMLight,we just define Application Resources Once in App.xaml.so we can bind all the window's properties simply,hope this can help some people who has the same problem!!
app.xaml
<Application.Resources>
<!--Global View Model Locator-->
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="True" />
</Application.Resources>
and in the window(view)
DataContext="{Binding DefaultSpecItemVM, Source={StaticResource Locator}}"
and it works perfect.:D

Related

Injecting sqlite database into MAUI ViewModels gives error that ViewModel does not define parameterless constructor

I am new to MAUI and I have a working project that uses an sqlite database to store my data for my project.
I am trying to inject my database access object into the ViewModel for one of my Content Pages.
I had it working previously by just creating ("new'ing up") my database access object and the database and project worked fine.
When I changed this so that I would inject my database access object into the ViewModel's constructor I get an error:
/Users/RemoteCommand/Projects/Notes/Views/AllNotesPage.xaml(9,9): Error: XLS0507: Type 'AllNotes' is not usable as an object element because it is not public or does not define a public parameterless constructor or a type converter. (Notes) IntelliSense
Here is my XAML file:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:Notes.Models"
x:Class="Notes.Views.AllNotesPage"
Title="AllNotesPage">
<ContentPage.BindingContext>
<models:AllNotes />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<!--<ToolbarItem Text="Add" Clicked="Add_Clicked" IconImageSource="{FontImage Glyph='+', Color=White, Size=22}"/>-->
<ToolbarItem Text="Add" Command="{Binding AddClickedCommand}" IconImageSource="{FontImage Glyph='+', Color=White, Size=22}"/>
</ContentPage.ToolbarItems>
<CollectionView x:Name="notesCollection"
ItemsSource="{Binding Notes}"
Margin="20"
SelectionMode="Single"
SelectionChanged="notesCollection_SelectionChanged">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="10"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Text}" FontSize = "22"/>
<Label Text="{Binding Date}" FontSize="14" TextColor="Silver"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
Here is my code behind:
namespace Notes.Views;
using Notes.Models;
using Notes.Views;
using Notes.Data;
using CommunityToolkit.Mvvm.Input;
public partial class AllNotesPage : ContentPage
{
public AllNotesPage()
{
InitializeComponent();
}
async void notesCollection_SelectionChanged(System.Object sender, Microsoft.Maui.Controls.SelectionChangedEventArgs e)
{
if(e.CurrentSelection.Count != 0)
{
var note = (Note)e.CurrentSelection[0];
await Shell.Current.GoToAsync($"{nameof(NotePage)}?{nameof(NotePage.ItemId)}={note.ID}");
}
}
}
Here is my ViewModel:
using System;
using System.Collections.ObjectModel;
using Notes.Data;
using Notes.Views;
using CommunityToolkit.Mvvm;
using CommunityToolkit.Mvvm.Input;
namespace Notes.Models;
public partial class AllNotes
{
NotesDatabase _notesDatabase;
public ObservableCollection<Note> Notes { get; set; } = new ObservableCollection<Note>();
public AllNotes(NotesDatabase notesDatabase)
{
_notesDatabase = notesDatabase;
LoadNotes();
}
[RelayCommand]
async void AddClicked()
{
await Shell.Current.GoToAsync(nameof(NotePage));
}
public async void LoadNotes()
{
Notes.Clear();
List<Note> notes = await _notesDatabase.GetItemsAsync();
foreach(Note note in notes)
{
Notes.Add(note);
}
}
}
and here is my MauiProgram where I define the dependency injection:
using Microsoft.Extensions.Logging;
using Notes.Views;
using Notes.Models;
using Notes.Data;
namespace Notes;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
builder.Services.AddSingleton<AllNotes>();
builder.Services.AddSingleton<AllNotesPage>();
builder.Services.AddSingleton<NotesDatabase>();
return builder.Build();
}
}
[Note: I have switched the AddSingleton to Add Transient for this page for these definitions to see if that would fix the problem but it did not]
I've tried a really basic dependency injection on an earlier test project where I injected my data access object into the code behind and I got the same error about missing a parameterless constructor and it turns out what I was missing was defining my data access object AND ContentPage as a Transient or Singleton in MauiProgram (Which is why for this project I added the data access object, ContentPage, and ViewModel as Singletons to the MauiProgram). Once I did that it worked, but now that I am using a ViewModel that I bind in the XAML I can't seem to get DI working for the ViewModel.
Please help this greenhorn!
Sincerely,
Doing this in XAML is an open issue: Resolve XAML BindingContext from ServiceCollection.
For now, do this via code behind's constructor, with a parameter:
public AllNotesPage(AllNotes vm)
{
InitializeComponent();
BindingContext = vm;
}
DI will inject vm, doing the needed instantiation of AllNotes and its NotesDatabase.

Attached Property binding not working for Flayout (Xaml, UWP)

I've faced a problem: Attached property doesn't work for Button's Flayout.
I have an outer button with inner button (flayout), attached property - is a ICommand type property. Outer button successfully binds to attached property BUT inner button doesn't.
Here User Control code:
<UserControl x:Class="uwp_AttachedProperty.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:uwp_AttachedProperty"
Name="Root">
<Button Command="{Binding ElementName=Root, Path=(local:AttachedProps.CommandAdd), Mode=OneWay}">
<Button.Flyout>
<Flyout>
<!--*** DOESN'T WORK HERE***-->
<Button Command="{Binding ElementName=Root, Path=(local:AttachedProps.CommandAdd),Mode=OneWay}"/>
</Flyout>
</Button.Flyout>
</Button>
</UserControl>
Attached property code:
public sealed class AttachedProps : DependencyObject
{
public static readonly DependencyProperty CommandAddProperty = DependencyProperty.Register(
"CommandAdd",
typeof(ICommand),
typeof(AttachedProps),
new PropertyMetadata(null));
public static void SetCommandAdd(UIElement element, ICommand value) { element.SetValue(CommandAddProperty, value); }
public static ICommand GetCommandAdd(UIElement element) { return (ICommand)element.GetValue(CommandAddProperty); }
}
My case is an ICommand propery, but it doesn't work any type (double, string etc.) of attached property.
Does any one faced the same problem, how can it be fixed?
(Windows 10, 1809, Build: 17763)
Thanks.
Attached Property binding not working for Flayout (Xaml, UWP)
Derive this case reply The problem is you used Binding ElementName=Root in the Flyout, When you bind data in the content of Flyout, the binding source is in Page, but the actual binding target is in PopupRoot, they have different DataContext, so can't it work here.

Unable to add PopupPage to project in Visual Studio

I would like to use Rg.Plugins.Popup for Xamarin.Forms but unfortunately I cannot add PopupPage to the project. I am using VIsual Studio 2017. In AddNewItem window there is no PopupPage at all.
I tried to add ContentPage instead like this:
public partial class CustomPopupPage : ContentPage
{
public CustomPopupPage ()
{
InitializeComponent ();
}
}
but whenever i try to change type ContentPage to PopupPage I get following error: Partial declarations of 'CustomPopupPage' must not specify different base classes.
The problem is that second partial class is in auto-generated file CustomPopupPage.xaml.g.cs and I cannot modify that file because each time application is compiling it rewrites that file.
I think I am missing something obvious here because demo is working fine.
PopupPage is a subclass of ContentPage .So you have to add a new ContentPage and change the superclass both in xaml and code benind .
Firstly , install the package Rg.Plugins.Popup from nuget in share project and specific platform (iOS and Android).
The plugin requires to be initialized. To use a PopupPage inside an application, each platform application must initialize the Rg.Plugins.Popup. This initialization step varies from platform to platform and is discussed in the following sections.
iOS ->AppDelegate.cs
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Rg.Plugins.Popup.Popup.Init();
global::Xamarin.Forms.Forms.Init ();
LoadApplication (new App ());
return base.FinishedLaunching (app, options);
}
Android->MainActivity
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Rg.Plugins.Popup.Popup.Init(this, bundle);
Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication (new App ());
}
xaml
<?xml version="1.0" encoding="utf-8" ?>
<pages:PopupPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup"
xmlns:animations="clr-namespace:Rg.Plugins.Popup.Animations;assembly=Rg.Plugins.Popup"
x:Class="MyProject.MyPopupPage">
<!--You can set an animation in the xaml file or in the csharp code behind-->
<pages:PopupPage.Animation>
<animations:ScaleAnimation
PositionIn="Center"
PositionOut="Center"
ScaleIn="1.2"
ScaleOut="0.8"
DurationIn="400"
DurationOut="300"
EasingIn="SinOut"
EasingOut="SinIn"
HasBackgroundAnimation="True"/>
</pages:PopupPage.Animation>
<!--You can use any elements here which are extended from Xamarin.Forms.View-->
<StackLayout
VerticalOptions="Center"
HorizontalOptions="Center"
Padding="20, 20, 20, 20">
<Label
Text="Test"/>
</StackLayout>
</pages:PopupPage>
in code behind
public partial class MyPopupPage : Rg.Plugins.Popup.Pages.PopupPage
{
public MyPopupPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
}
}
Update
It seems an existing issue of vs 2017 , on VS 2019 it works fine . And I will post this issue to product teams .

Set focus on Entry field in Xamarin.Forms MVVM (Prism)

I'm using Xamarin.Forms and Prism to create my mobile app.
I have a screen with 2 entries. When entering the screen, I'd like to set the focus on the first entry.
Then after the user entered data in this entry and validated it, I'd like to set the focus to the second entry.
Based on first answer:
I should do something wrong. I've created a small new Prism project to test it :
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:testEntry"
x:Class="testEntry.Views.MainPage"
Title="{Binding Title}">
<StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<Label Text="Welcome to Xamarin Forms and Prism!" />
<local:MyEntry Placeholder="" x:Name="entry1" />
<Button Text="set focus on entry1" Clicked="Button_Clicked"/>
</StackLayout>
</ContentPage>
MainPage.xaml.cs
using Xamarin.Forms;
namespace testEntry.Views
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
entry1.Focus(); //Not Working
}
private void Button_Clicked(object sender, EventArgs e)
{
entry1.Focus(); //Working
}
}
}
MyEntry.cs (in Main project)
using Xamarin.Forms;
namespace testEntry
{
public class MyEntry : Entry
{
}
}
MyEntryRenderer.cs (in Android Project)
using Android.Content;
using Android.Views;
using Android.Views.Accessibility;
using Xamarin.Forms.Platform.Android;
namespace testEntry.Droid
{
public class MyEntryRenderer : EntryRenderer
{
public MyEntryRenderer(Context context) : base(context)
{
}
public static void Focus(View view)
{
view.SendAccessibilityEvent(EventTypes.ViewFocused);
}
}
}
Unfortunately, still nofocus on my field :'(
Finally, and thanks to Saamer, I found another way of doing it by using EventAggregator.
public class FocusChanged : PubSubEvent<String> { }
Then in my view model :
IEventAggregator _ea;
public MainPageViewModel(INavigationService navigationService, IEventAggregator eventAggregator) : base(navigationService)
{
_ea = eventAggregator;
}
In the viewModel, whenever I want to set the focus to a field, I'm sending an event :
_ea.GetEvent<FocusChanged>().Publish("Source");
And in my view's code behind, I handle this event:
IEventAggregator _ea;
public MainPage(IEventAggregator eventAggregator)
{
InitializeComponent();
_ea = eventAggregator;
_ea.GetEvent<FocusChanged>().Subscribe(SetFocusOnControl); //Name of method which will handle this event
}
/// set the focus on entry based on parameter
/// each event value will set focus on a specific entry (example: source is linked to entry txtScanSrc)
private async void SetFocusOnControl(String fieldName)
{
Entry l_view;
switch(fieldName)
{
case "source": l_view = this.FindByName<Entry>("txtScanSrc"); break;
case "quantity": l_view = this.FindByName<Entry>("txtQty"); break;
case "tote": l_view = this.FindByName<Entry>("txtScanTote"); break;
case "pallet": l_view = this.FindByName<Entry>("txtScanPalout"); break;
case "destination": l_view = this.FindByName<Entry>("txtScanDest"); break;
default: l_view = this.FindByName<Entry>("txtScanSrc"); break;
}
await WaitAndExecute(500, () => { l_view.Focus(); });
}
There's a way of doing this using the Accessibility APIs of each of the platforms. Xamarin forms doesn't have all the platform features of accessibility yet so you d have to create a custom renderer and then call the focus method in a life cycle event of the page.
So calling this Focus function would cause the app to focus on that element. You generally don't want to do it because the app purposefully focuses on what it does so accessible users have a consistent experience. But if you really want to override the default behavior, in Android it's something like this
public static void Focus(View view)
{
view.SendAccessibilityEvent(EventTypes.ViewFocused);
}
And in iOS, you have to use the PostNotification apis which will be some variation of this
UIAccessibility.PostNotification(UIAccessibilityPostNotification.ScreenChanged, entry element)
You can look more into Accessibility Focus to get the exact answer

xamarin.forms Handle Clicked event on WebView

I want to handle the click/tap event on a WebView control
I've tried the GestureRecognizers but nothing happens, i think maybe the WebView has some sort of making the event handled "true".
<WebView>
<WebView.GestureRecognizers>
<TapGestureRecognizer
Tapped="OnWebViewClick"
NumberOfTapsRequired="1" />
</WebView.GestureRecognizers>
</WebView>
And I've tried it using c# code behind too but no luck :/
I am using this hack: put the WebView in a Grid and insert a BoxView in the same cell. Then put a handler on the BoxView.
You can use the HybridWebView from XLabs, and use javascript injection to invoke and handle clicks in your Xamarin control. The injected javascript code can add a click-event listener at capture stage. When a click is detected it uses Native callback to send information back to C# code.
For example - you can define a custom control like this:
public class ClickEventArgs : EventArgs
{
public string Element { get; set; }
}
public class ClickableWebView : XLabs.Forms.Controls.HybridWebView
{
public event EventHandler<ClickEventArgs> Clicked;
public static readonly BindableProperty ClickCommandProperty =
BindableProperty.Create("ClickCommand", typeof(ICommand), typeof(ClickableWebView), null);
public ICommand ClickCommand
{
get { return (ICommand)GetValue(ClickCommandProperty); }
set { SetValue(ClickCommandProperty, value); }
}
public ClickableWebView()
{
LoadFinished += (sender, e) =>
InjectJavaScript(#"
document.body.addEventListener('click', function(e) {
e = e || window.event;
var target = e.target || e.srcElement;
Native('invokeClick', 'tag='+target.tagName+' id='+target.id+' name='+target.name);
}, true /* to ensure we capture it first*/);
");
this.RegisterCallback("invokeClick", (string el) => {
var args = new ClickEventArgs { Element = el };
Clicked?.Invoke(this, args);
ClickCommand?.Execute(args);
});
}
}
Sample XAML usage
<local:ClickableWebView
Uri="https://google.com"
Clicked="Handle_Clicked"
/>
and sample code-behind
void Handle_Clicked(object sender, CustomWebView.ClickEventArgs e)
{
Xamarin.Forms.Application.Current.MainPage.DisplayAlert("WebView Clicked", e.Element, "Dismiss");
}
** Output **
Alternatively, you can also bind ClickCommand property to implement this using MVVM pattern.
Another option is to handle the click in html and do a navigation that doesn't go anywhere. You can put something like this in your html
<div onclick="window.location.href='#click#'">...</div>
So a click anywhere inside there would cause a navigation. If you only have a button, you could just use
<a href='#click'>...</a>
Then in your WebView control wire up the Navigating event, and check if the new url contains "#click". If so, do your click handling code and call e.Cancel=true in the event to prevent the browser from completing the navigation.
Note that onclick handlers don't work on body or document elements in Xamarin Webview. At least not on iOS.
I've found the simplest approach is to use a Grid with two controls, the WebView and a Button
<Grid>
<WebView
Grid.Column="0"
Grid.Row="0"
HeightRequest="100"
WidthRequest="1000" />
<Button
Grid.Column="0"
Grid.Row="0"
BackgroundColor="Transparent"
HorizontalOptions="Fill"
VerticalOptions="Fill"
Clicked="OnWebViewTapped"/>
</Grid>
The button covers the WebView and intercepts taps.
Gesture recognizer doesn't work with WebView. You can try using MR.Gestures
To get all the features you will have to purchase a license.
If you forget to configure the license key properly or the key does not match your app name, then all the events will still be raised, but the properties of the EventArgs will be empty. That may be enough for the tap and long press events, but not for the more complicated ones.
An easier workaround is to use the 'Focused' event on your webview. You can implement it as below:
var wb = new WebView
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
Source = "https://stackoverflow.com/questions/56320611/webview-gesturerecognition-not-working-in-xamarin-forms",
};
wb.Focused += (s, e) =>
{
//Handle your logic here!
wb.Unfocus();
};

Resources