Unable to reference custom Xamarin PlatformEffect in XAML - c#

====SOLVED====
Joe's answer was spot on and I was able to get this working using his advice. I basically just had to add the effect in my code behind and it worked.
Using information found on the net, I created a PlatformEffect for iOS that can be assigned to any element. It adjusts the view to take into account an iPhone notch, if present.
My problem is, I am unable to reference the platform effect in my XAML.
Theortically, given the code at the bottom of this message, I should be able to use the following to apply the effect:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Enterprise.View.Features.Authentication.LoginView"
xmlns:effect="clr-namespace:Enterprise.iOS.Effects">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentView BackgroundColor="Green">
<ContentView.Effects>
<effect:SafeAreaPaddingEffect />
</ContentView.Effects>
<Label Text="Hello, from XamarinHelp.com" />
</ContentView>
</Grid>
</ContentPage>
However, the effect reference cannot be resolved in the ContentPage declaration. I'm probably doing something wrong, but I'm not sure what it is. I have not found anything via searches that answer my question.
Any thoughts? Here is the PlatformEffect file (located under an Effects folder in the iOS solution):
using Enterprise.iOS.Effects;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ResolutionGroupName("Enterprise.iOS")]
[assembly: ExportEffect(typeof(SafeAreaPaddingEffect), nameof(SafeAreaPaddingEffect))]
namespace Enterprise.iOS.Effects
{
public class SafeAreaPaddingEffect : PlatformEffect
{
Thickness _padding;
protected override void OnAttached()
{
if (Element is Layout element)
{
if (UIDevice.CurrentDevice.CheckSystemVersion(11, 0))
{
_padding = element.Padding;
var insets =
UIApplication.SharedApplication.Windows[0].SafeAreaInsets; // Can't use KeyWindow this early
if (insets.Top > 0) // We have a notch
{
element.Padding = new Thickness(_padding.Left + insets.Left, _padding.Top + insets.Top,
_padding.Right + insets.Right, _padding.Bottom);
return;
}
}
// Uses a default Padding of 20. Could use an property to modify if you wanted.
element.Padding = new Thickness(_padding.Left, _padding.Top + 20, _padding.Right, _padding.Bottom);
}
}
protected override void OnDetached()
{
if (Element is Layout element)
{
element.Padding = _padding;
}
}
}
}

To implement an effect for usage in a XAML Xamarin Forms project the following classes might be defined:
MyEffects.EffectIds (namespace MyEffects, class name EffectIds) located in a Xamarin Forms or a Netstandard project (e.g. MyProject) to define the identifier of the Effect.
MyEffects.MyEffect (namespace MyEffects, class name MyEffect) located in a Xamarin Forms project or a Netstandard project (e.g. MyProject) to define the Xamarin Forms effect.
MyEffects.iOS.MyEffect (namespace MyEffects.iOS, class name MyEffect) located in an iOS project to implement the iOS effect.
Sample MyEffects.EffectIds:
using Xamarin.Forms;
[assembly: ResolutionGroupName(MyEffects.EffectIds.GroupName)]
namespace MyEffects
{
public class EffectIds
{
public const string GroupName = "MyEffects";
public static string MyEffect => typeof(MyEffect).FullName;
//another effect not defined here
public static string MyOtherEffect => typeof(MyOtherEffect).FullName;
...
Sample MyEffects.MyEffect:
using Xamarin.Forms;
namespace MyEffects
{
public class MyEffect : RoutingEffect
{
public MyEffect() : base(EffectIds.MyEffect) { }
}
}
Sample MyEffects.iOS.MyEffect:
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using PlatformEffects = MyEffects.iOS;
using RoutingEffects = MyEffects;
[assembly: ExportEffect(typeof(PlatformEffects.MyEffect), nameof(RoutingEffects.MyEffect))]
namespace MyEffects.iOS
{
public class MyEffect : PlatformEffect
{
protected override void OnAttached()
{
...
Sample usage in XAML:
<ContentPage
xmlns:effects="clr-namespace:MyEffects;assembly=MyProject"
...
<Entry ... >
<Entry.Effects>
<effects:MyEffect />
</Entry.Effects>
</Entry>

I was able to fix this by assigning the effect in the code behind. So the relevant XAML not looks like the following
<ContentView BackgroundColor="Green">
<Label x:Name="HelloLabel" Text="Hello, from XamarinHelp.com" />
</ContentView>
and in my code behind, I added the followin immediately after initializing the component
HelloLabel.Effects.Add(Effect.Resolve("Enterprise.iOS.Effects.SafeAreaPaddingEffect"));

Related

Assigning image resource during runtime for xaml .Net 2.0 multi platform mobile development

Goal:
I want to display an ImageButton with an image just like this:
The current solution is working but I can only choose to set a single image hardcoded into xaml.
For this purpose I have already prepared multiple things:
Current Situation
I have added the image and set it as "Build Action: Embedded Resource"
I have added ImageResourceExtension just as suggested online for use in xaml:
using System;
using System.Reflection;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Bitconomy.resources
{
[ContentProperty(nameof(Source))]
public class ImageResourceExtension : IMarkupExtension
{
public string Source { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Source == null)
{
return null;
}
// Do your translation lookup here, using whatever method you require
var imageSource = ImageSource.FromResource(Source, typeof(ImageResourceExtension).GetTypeInfo().Assembly);
return imageSource;
}
}
}
I have added reference to the ImageResourceExtension/resources in xaml:
xmlns:resources="clr-namespace:Bitconomy.resources"
I have added the image resource to the ImageButton:
<ImageButton x:Name="MiningButton" Grid.Row="0" Source="{resources:ImageResource Bitconomy.resources.images.mines.stone_mine.jpg}" ></ImageButton>
The result is the one shown in the top under the Goal section. fine so far.
Issue
I would like to set up the mines on the go. I have prepared a "mining_view.xaml". Within "mining_view.xaml.cs", I have added code to configure the mine, eg. for stone, iron, or whatever it will be. I want to have different images for each mine.
I have had a similar question in the past available on my stack overflow but it was for desktop use. I was shocked how different mobile development is after all. Similar but not the same.
I'd be very happy if you could help me figure out on how to set the image source in code so it will work in both, android and ios.
public void ConfigureMine(string ItemType)
{
this.MiningButton.Source = new ImageSource.FromResource("resources:ImageResource Bitconomy.resources.images.mines.stone_mine.jpg");
// or sth like that?
this.MiningButton.Source = resources.ImageResourceExtension.ProvideValue(SomeProvider?)
}
create an image in xaml. Assign an x:Name property to select the image from code:
<Image x:Name="Mine_Image" Grid.Column="0" Grid.Row="0" Aspect="AspectFill"></Image>
You will need an resource extension:
using System;
using System.Reflection;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Bitconomy.resources
{
[ContentProperty(nameof(Source))]
public class ImageResourceExtension : IMarkupExtension
{
public string Source { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Source == null)
{
return null;
}
// Do your translation lookup here, using whatever method you require
var imageSource = ImageSource.FromResource(Source, typeof(ImageResourceExtension).GetTypeInfo().Assembly);
return imageSource;
}
public ImageSource GetImageSource(string resourcepath)
{
return ImageSource.FromResource(resourcepath, typeof(ImageResourceExtension).GetTypeInfo().Assembly);
}
}
}
add resource image
Set image to build action "Embedded Resource"
Change Image within form.xaml.cs (adjust the resource path for your needs):
this.Mine_Image.Source = Resources.GetImageSource("Bitconomy.resources.images.resources.Stone_mine.jpg");

Xamarin.Forms ListView Binding Issue

We are a newbie for Xamarin. We are having an issue in binding the response data from a web service to a ListView.
We debugged and we can see the the web service is successfully responding with the data but it never gets populated.
Any ideas?
It's gotta be a small thing that we are missing. We have managed to display a single entry from the data with other views (in other parts of the project) BUT not in IEnumerable<> or List<>
Here's the code:
View - RoundsPage.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:viewModels="clr-namespace:AthlosifyMobile.ViewModels"
x:Class="AthlosifyMobile.Views.RoundsPage">
<ContentPage.BindingContext>
<viewModels:RoundsViewModel />
</ContentPage.BindingContext>
<StackLayout>
<Entry Text="{Binding AccessToken}" />
<Button Command="{Binding GetRoundsCommand}" Text="Get all rounds" />
<Label Text="Rounds: "></Label>
<ListView ItemsSource="{Binding Rounds}" HasUnevenRows="true" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="Round 1:"></Label>
<Label Text="{Binding Name}"></Label>
<Label Text="{Binding DailyHandicap}"></Label>
<Label Text="{Binding PlayedUTC}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>`
ViewModel - RoundsViewModel.cs :
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using AthlosifyMobile.Annotations;
using Xamarin.Forms;
using AthlosifyMobile.Services;
using AthlosifyMobile.Models;
namespace AthlosifyMobile.ViewModels
{
public class RoundsViewModel : INotifyPropertyChanged
{
ApiServices _apiServices = new ApiServices();
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<Round> _rounds;
public string AccessToken { get; set; }
public IEnumerable<Round> Rounds
{
get
{
return _rounds;
}
set
{
_rounds = value;
OnPropertyChanged();
}
}
public ICommand GetRoundsCommand
{
get
{
return new Command(async() =>
{
Rounds = await _apiServices.GetRoundsAsync(AccessToken);
});
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Model - Course.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace AthlosifyMobile.Models
{
public class Round : EntityBase
{
public Guid RoundID { get; set; }
public Guid UserID { get; set; }
public Guid RoundCategoryID { get; set; }
public Guid CourseID { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public int DailyHandicap { get; set; }
public DateTime PlayedUTC { get; set; }
public RoundCategory RoundCategory { get; set; }
public Course Course { get; set; }
public ICollection<RoundHole> RoundHoles { get; set; }
}
public abstract class EntityBase
{
public DateTime CreatedUTC { get; set; }
public DateTime LastModifiedUTC { get; set; }
}
}
Services - apiservices.cs:
using AthlosifyMobile.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace AthlosifyMobile.Services
{
public async Task<IEnumerable<Round>> GetRoundsAsync(string accessToken)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var json = await client.GetStringAsync("http://localhost:5609/api/Rounds");
var list = JsonConvert.DeserializeObject<IEnumerable<Round>>(json);
return list;
}
}
}
You will need to diagnose whether this is an issue with connecting the View to the ViewModel or whether your Data Service isn't working correctly. Either way, there are a few things you should do to fix this!
Firstly you are using IEnumerable, instead you should be using ObservableCollection<T>. You should always be using ObservableCollection<T> for Binded list views. This is explained in the xamarin docs here (they automatically notify the view when their contents changed & update).
So you should make this change:
public ObservableCollection<Round> Rounds { get; }
Next you should verify that the bindings are correct. I would not recommend your approach of going straight to live data if you aren't familiar with xamarin. Firstly you should try adding some static objects to the view model and trying to bind them!
Disconnect your API code and call a method that creates some of your Round objects. Here is an example method (i use methods like these all the time when designing my ListViews UI).
public RoundsViewModel()
{
Rounds = CreateSampleData();
}
private ObservableCollection<Round> CreateSampleData()
{
ObservableCollection<Round> dummyData = new ObservableCollection<Round>();
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
return dummyData;
}
At this point you will either see items in your ListView, meaning you have an issue with your API code / Implementation of INotifyPropertyChanged. If you don't see anything then you likely have an issue with binding and will need to verify that your view is actually connected to the View Model.
Mvvm Helpers
Seeing some of this code makes me feel very sorry for you, you definitely should looking into using an MVVM helper such as Prism or MVVMCross. I personally use Prism which provides a ViewModelBase which all ViewModels inherit from. This means all of the INotifyPropertyChanged code is hidden away from you (less boilerplate). It also has a dependancy service which means hooking views up to view models is as simple as registering it in the app.cs.
If you are interested in prism, watch this video with Brian Lagunas to see what Prism can do for you!
Update: There are now a few helpful libraries aside from Prism that will help with the MVVM stuff. Refractored.MVVMHelpers and Xamarin.CommunityToolkit both contain an essential object: ObservableRangeCollection.
ALL code using an ObservableCollection should be replaced with ObservableRangeCollection, it is an essential object and really belongs in a microsoft maintained namespace at this point. It creates a performance benefit for updating larger collections & reduces the need for alot of boilerplate when updating the ObservableCollection

Xamarin: Binding property not found

This app works just fine in UWP. I have ripped out everything except one of the more basic properties that is failing on Android. It looks like this:
MyPage.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:ViewModels="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.Views.MyApp">
<ContentPage.BindingContext>
<ViewModels:MyViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<ScrollView>
<StackLayout Style="{StaticResource PageForm}">
<Picker ItemsSource="{Binding Modes}"
ItemDisplayBinding="{Binding Value}"
SelectedItem="{Binding SelectedMode}" />
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
MyPage.cs
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace MyApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyApp : ContentPage
{
public MyApp ()
{
InitializeComponent ();
}
}
}
MyViewModel.cs
using MyApp.Models;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace MyApp.ViewModels
{
public class MyViewModel: INotifyPropertyChanged
{
List<Mode> _modes;
Mode _selectedMode;
public event PropertyChangedEventHandler PropertyChanged;
public MyViewModel()
{
Modes = new List<Mode>()
{
new Mode() { Key=ModeType.Mode1, Value="Mode1" },
new Mode() { Key=ModeType.Mode2, Value="Mode2" }
};
SelectedMode = Modes.Single(m => m.Key == ModeType.Mode1);
}
public List<Mode> Modes {
get { return _modes; }
set {
_modes = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Modes"));
}
}
public Mode SelectedMode {
get {
return _selectedMode;
}
set {
_selectedMode = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SelectedMode"));
}
}
}
}
Mode.cs
namespace MyApp.Models
{
public enum ModeType { Mode1, Mode2 };
public class Mode
{
public ModeType _key;
public string _value;
public ModeType Key {
get
{
return _key;
}
set
{
_key = value;
}
}
public string Value {
get
{
return _value;
}
set
{
_value = value;
}
}
}
}
and what I see in the Debug console is
[0:] Binding: 'Value' property not found on 'MyApp.Models.Mode', target property: 'Xamarin.Forms.Picker.Display'
[0:] Binding: 'Value' property not found on 'MyApp.Models.Mode', target property: 'Xamarin.Forms.Picker.Display'
[0:] Binding: 'SelectedMode' property not found on 'MyApp.ViewModels.'MyApp', target property: 'Xamarin.Forms.Picker.SelectedItem'
Like I said this works if I run it as a UWP app but when I try it on Android it just doesn't work. That's about all I can say since it doesn't really say what the problem is other than the errors above which don't make sense.
The rest of the view model actually works. The main part of the app works, I can even run the code on this view model. If I create a simple string binding that will work, even on Android.
Any help is appreciated.
The answer is total magic to me. If someone can please explain this I will mark your answer as the accepted one.
Anroid Project File > Properties > Linking > Set to None.
It still didn't work so I closed Visual Studio and deleted the bin and obj directories in the PCL and Android projects. Finally it worked.
One other thing is this seems like I've now lost the ability to have linking be set to sdk and user assemblies. What if I need that at some point?
Use a one way binding to avoid having these binding errors in the debug console.
Text="{Binding [Name], Source={x:Static i18n:Translator.Instance}, Mode=OneWay}"
If you need TwoWay binding, make sure the bound model objects implement INotifyPropertyChanged as Markus Michel indicated.
Your mode model class also needs to implement INotifyPropertyChanged

Why can't I use this command in my resource dictionary?

I cannot for the life of me figure out why I cannot create my class in this dictionary. Intellisense isn't picking up my WindowCommand<T> class. I checked the Assembly name and it appears to be correct, no typos in the namespace either. What's making it choke?
WindowCommand.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using Ninject;
using Premier;
using Premier.View;
namespace Premier.Command
{
public class WindowCommand<T> : Command where T : Window
{
private Func<bool> focus;
private int instantiationCount;
public bool IsDialog { get; set; }
public bool Multiple { get; set; }
public WindowCommand()
{
}
public override bool CanExecute(object parameter)
{
return true;
}
public override void Execute(object parameter)
{
var instantiatedOnce = instantiationCount > 0;
if (!Multiple && instantiatedOnce)
{
focus();
return;
}
instantiationCount++;
var w = App.Kernel.Get<T>();
w.Closed += (s, e) => instantiationCount--;
focus = w.Focus;
if (IsDialog)
w.ShowDialog();
else
w.Show();
}
}
}
Windows.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:c="clr-namespace:Premier.Command;assembly=PremierAutoDataExtractor"
xmlns:v="clr-namespace:Premier.View;assembly=PremierAutoDataExtractor"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<c:WindowCommand x:Key="ReportsPurchased" x:TypeArguments="v:PurchasedReportsView" />
</ResourceDictionary>
x:TypeArguments XAML directive is not supported in XAML 2006 (xml namespace http://schemas.microsoft.com/winfx/2006/xaml/presentation) on non-root XAML elements. If you want to use x:TypeArguments on a non-root XAML element, you should use XAML2009 (xml namespace http://schemas.microsoft.com/netfx/2009/xaml/presentation). However, again it is only supported for non-complied loose XAML.
Text from MSDN Page:
In WPF and when targeting .NET Framework 4, you can use XAML 2009
features together with x:TypeArguments but only for loose XAML (XAML
that is not markup-compiled). Markup-compiled XAML for WPF and the
BAML form of XAML do not currently support the XAML 2009 keywords and
features. If you need to markup compile the XAML, you must operate
under the restrictions noted in the "XAML 2006 and WPF Generic XAML
Usages" section.
So, I am afraid, you cannot use your WindowCommand in a resource dictionary.
Link to MSDN page for more information on x:TypeArguments directive.

MVVM - Open Second View Windows 8

Edit: Example App -> http://www10.zippyshare.com/v/29730402/file.html
I'm programming an app for Windows 8 & Windows Phone. I'm using the portable class library (see this article http://blog.tattoocoder.com/2013/01/portable-mvvm-light-move-your-view.html).
My problem is: How can I open a second window by clicking on a button by using the MVVM-pattern? I don't want to do it in the behind-code.
My datacontext for the Windows 8 app looks in the xaml like this
DataContext="{Binding Main, Source={StaticResource Locator}}"
which uses the ViewModel of the PCL (= ViewModel for both, W8 & WP8)
xmlns:vm="using:Mvvm.PCL.ViewModel"
I don't know how to assign 2 datacontext to my MainPage.xaml, nor do I know how to assign my MainPage.xaml to the ViewModel for my Windows 8 app.
I've tried something like this:
Command="{Binding DisplayView}" CommandParameter="SecondView"
but the program uses the ViewModel for both platforms and I can't program there the windows-assignment for the specific platforms. (it should look something like this Opening multiple views by clicking button using MVVM silverlight approach ...)
To make it clear:
I have 2 projects.
Both MainWindows of the projects refer to the ViewModel of the "MainProject".
If I want to click on a button in my MainWindow, I want to open a new view, but I can only use the ViewModel for both projects, which means that I can't use any views of the 2 projects in my ViewModel of the "MainProject".
edit: I've seen that many people use ContentControl. (Still doesn't work. Btw im new to MVVM).
<ContentControl Grid.Row="2" Content="{Binding CurrentView}" IsTabStop="False" Margin="10" />
<Button Command="{Binding DisplayView}" CommandParameter="SecondView">
MainViewModel.cs (For both platforms)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using Mvvm.PCL.Model;
#if NETFX_CORE
using Mvvm.Store.Views;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#endif
namespace Mvvm.PCL.ViewModel
{
public class MainViewModel : ViewModelBase, INotifyPropertyChanged
{
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
#if NETFX_CORE
DisplayView = new RelayCommand<string>(DisplayViewCommandExecute);
#endif
}
#region Commands
public RelayCommand<string> DisplayView { get; private set; }
#endregion
#if NETFX_CORE
#region CurrentView Property
public const string CurrentViewPropertyName = "CurrentView";
private Page _currentView;
public Page CurrentView
{
get { return _currentView; }
set
{
if (_currentView == value)
return;
_currentView = value;
RaisePropertyChanged(CurrentViewPropertyName);
}
}
private SecondView _secondview = new SecondView();
public SecondView SecondView
{
get
{
return _secondview;
}
}
#endregion
private void DisplayViewCommandExecute(string viewName)
{
switch (viewName)
{
case "SecondView":
CurrentView = _secondview;
var frame = (Frame)Window.Current.Content;
frame.Navigate(typeof(SecondView));
break;
}
}
#endif
}
}

Categories

Resources