How do you retrieve User Defined Runtime Attributes in Xamarin? - c#

On my XCode storyboard I have a custom image view (called CustomImageButton). There is a section about User Defined Runtime Attributes:
I've seen a couple of examples about how to retrieve these values in Objective-C but can't work out how to convert to Xamarin C#. The code I'm using is:
class CustomImageButton : UIImageView
{
public CustomImageButton(IntPtr handle) : base(handle)
{
NSObject objType = ValueForKey(new NSString("bgType"));
if (objType == null)
// Do something here
}
public override NSObject ValueForUndefinedKey(NSString key)
{
return null;
}
}
However, it always goes into the undefined key part and returns null. How can I retrieve the string 'Transparent' for the key 'bgType'?

In your Custom (C#-based) UI class, add a property for your custom User Defined Runtime Attribute.
[Connect]
private NSString Stack {
get {
return (NSString)GetNativeField ("Stack");
}
set {
SetNativeField ("Stack", value);
}
}
This will in turn auto-generate the property in your ObjC custom class header file:
#property (nonatomic, retain) IBOutlet NSString *Stack;
Defined your attribute value:
When the xib or storyboard is loaded, this property gives the custom attribute a place to be stored for future reference.
Now you can access this value via ValueForKey:
partial void OnBottonUpInside (CustomButton sender) {
NSString value = (NSString)sender.ValueForKey(new NSString("Stack"));
Console.WriteLine(value);
}
Output Example:
2016-02-03 08:27:51.666 keyvalue[46492:2538387] OverFlow
Of course, I could have defined the property as public and just access it that way:
partial void OnBottonUpInside (CustomButton sender)
{
Console.WriteLine (sender.Stack);
}
Note: Normally in ObjC you would just define this as a strong property, but Xamarin does not define it that way, but the result is the "same":
#property (strong) NSString *Stack;

#SushuHangover's response is correct and still works. However, if you want to avoid the deprecated GetNativeField() & SetNativeField() methods, then this would be the change that has worked for me:
[Connect]
private NSObject Stack
{
get
{
return ValueForKey((NSString)"Stack");
}
set
{
SetValueForKey(value, (NSString)"Stack");
}
}

Related

'The left-hand side of an assignment must be a variable, property or indexer' error with custom handler in .NET MAUI

I am currently migrating a Xamarin.Forms app to .NET MAUI, and I am noticing that to make custom controls it has changed to creating a custom handler. I have followed the documentation values and an article in the .NET MAUI Wiki on GitHub but I have come across when making the MapText that when assigning the value that I declare in my interface it throws me the error CS0131 "The left-hand side of an assignment must be a variable, property or indexer" and this prevents me from continuing.
For the moment I have continued using the classic way to create the custom controls, but since I have already indicated deprecated I have decided to adopt this new way, but the error mentioned above prevents me, in this case not only with a custom Entry, but also with a custom Label.
I would be very grateful for your support
This is my CustomEntryHandler:
public partial class CustomEntryHandler : ViewHandler<ICustomEntry, EditText>
{
private static PropertyMapper<ICustomEntry, CustomEntryHandler> CustomEntryMapper = new(ViewMapper)
{
[nameof(ICustomEntry.Text)] = MapText,
};
public CustomEntryHandler(CommandMapper commandMapper = null) : base(CustomEntryMapper, commandMapper)
{
}
protected override EditText CreatePlatformView()
{
return new EditText(Context);
}
private static void MapText(EntryHandler handler, ICustomEntry entry)
{
handler.PlatformView?.Text = entry.Text;// This line is the error CS0131
}
}
And this is my interface and Custom Control Class:
public interface ICustomEntry : IView
{
public string Text { get; }
public Color TextColor { get; }
}
public class CustomEntry : View, ICustomEntry
{
public string Text { get; set; }
public Color TextColor { get; set; }
}
You can change your code:
if(handler.PlatformView != null)
handler.PlatformView.Text = entry.Text;
The null propogation operator returns a value. Since, its on the left hand side and not a value, it cant be use this way.
Someone also opened an issue:
https://github.com/dotnet/csharplang/issues/2883

How to add a property to a control's DataBindings

I want to write a control derived from CheckedListBoxControl (DevExpress), and I need to add a property to the (DataBindings)
These are the standard Properties shown in the PropertyGrid:
So I can only choose between Tag and Text.
What I want is to add a third option called gttMasterField (which will be of type int, don't know if this matters)
I have been experimenting with the documentation but with no results.
These don't seem to cover exact what I am looking for, I don't know the correct search terms to find this, which makes it difficult to google for it. It will probably be somewhere in the documentation but also there I don't know on what terms to look for.
Create a Windows Forms user control that supports simple data binding
Create a Windows Forms user control that supports lookup data binding
Create a Windows Forms user control that supports complex data binding
Here is some code with comments that will also help to explain what I am searching for
public partial class gttDXManyToManyCheckedListBox : CheckedListBoxControl
{
private int _gttMasterField;
// This I want populated by setting the binding property MasterField
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; }
}
}
The project is a dotnet framework 4.7.2
To make a custom Property appear in the PropertyGrid's (DataBindings), decorate the Property with the BindableAttribute set to true:
[Bindable(true)]
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; }
}
Optionally also decorate with the desired DesignerSerializationVisibilityAttribute attribute
[Bindable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; }
}
The class can also specify the default bindable Property, settings a DefaultBindingPropertyAttribute:
[DefaultBindingProperty("gttMasterField")]
public partial class gttDXManyToManyCheckedListBox : CheckedListBoxControl
{
private int _gttMasterField;
[Bindable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; }
}
}
Important note:
the class should implement INotifyPropertyChanged -> a bindable Property is supposed to raise notification events. When the binding is set in the Designer, a BindingSource is generated to mediate the binding, but it requires that the objects involved send change notifications (mostrly to determine when the Property value is updated, usually as DataSourceUpdateMode.OnPropertyChanged).
For example:
using System.Runtime.CompilerServices;
[DefaultBindingProperty("gttMasterField")]
public partial class gttDXManyToManyCheckedListBox : CheckedListBoxControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _gttMasterField;
[Bindable(true)]
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; NotifyPropertyChanged(); }
}
private void NotifyPropertyChanged([CallerMemberName] string PropertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
}
Setting DesignerSerializationVisibility.Content implies initialization. If a BindingSource is used, this object supports initialization on itself, the Attribute is not strictly required.
It could be set to DesignerSerializationVisibility.Hidden, though, depending on the use case.

Change language runtime with wrapped language resources Xamarin Forms

We are developing several mobile apps, with Common .NET Standard library between them, which holds the common functionallity. (MVVM)
The Common project has a TranslationManager Class, and a Resource file, which holds the common translations.
TranslationManager uses constructor injection, to inject the app specific translation resources.
public TranslationManager(ResourceManager appSpecificLanguageResources)
{
_commonResources = CommonTranslationResources.ResourceManager;
_appSpecificLanguageResources = appSpecificLanguageResources;
}
With this code, we earn the possibilty to use common translations, and application specific translations with using only one Translation provider.
if (string.IsNullOrWhiteSpace(translationKey))
return null;
string commonTranslation = _commonResources.GetString(translationKey, new CultureInfo(_preferenceCache.CultureName));
string appSpecificTranslation = _appSpecificLanguageResources.GetString(translationKey, new CultureInfo(_preferenceCache.CultureName));
if (commonTranslation == null && appSpecificTranslation == null)
{
MobileLogger.Instance.LogWarning($"Translate could not found by translationKey: {translationKey}");
return $"TRANSLATION_{translationKey}";
}
if (!string.IsNullOrWhiteSpace(commonTranslation) && !string.IsNullOrWhiteSpace(appSpecificTranslation))
{
MobileLogger.Instance.LogDebug(TAG, $"Warning! Duplicate translate found for '{translationKey}' translationkey. Common translate is : '{commonTranslation}' , AppSpecific Translation is: {appSpecificTranslation}. Returning with appspecific translation.");
return appSpecificTranslation;
}
if (commonTranslation == null)
return appSpecificTranslation;
else
return commonTranslation;
In XAML, we have one MarkupExtension which provides the translation for the current language.
public class TranslateMarkupExtension : IMarkupExtension
{
public TranslateMarkupExtension()
{
}
public string TranslationKey { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (string.IsNullOrWhiteSpace(TranslationKey)) return "nullref";
return Resolver.Resolve<TranslationManager>().GetTranslationByKeyForCurrentCulture(TranslationKey);
}
}
XAML Usage seem to be like:
Entry Placeholder="{extensions:TranslateMarkup TranslationKey=PlaceholderPhoneNumber}"
The problem is, when i set the language at runtime, the translation extension markup does not evaluate the new translation.
Raising propertychanged with null parameter refreshes the bindings on the view, but does not affect MarkupExtensions.
I do not want to push the same page to the navigation stack, it seems patchwork for me.
The problem is, when i set the language at runtime, the translation extension markup does not evaluate the new translation.
you may need to use INotifyPropertychanged interface for TranslationManager,when you change UI culture, all string that are bound to the index would update.
More detailed info, please refer to:
Xamarin.Forms change UI language at runtime (XAML)
public class TranslateExtension : IMarkupExtension<BindingBase>
{
public TranslateExtension(string text)
{
Text = text;
}
public string Text { get; set; }
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return ProvideValue(serviceProvider);
}
public BindingBase ProvideValue(IServiceProvider serviceProvider)
{
var binding = new Binding
{
Mode = BindingMode.OneWay,
Path = $"[{Text}]",
Source = Translator.Instance,
};
return binding;
}
}
and this the Translator class as initially proposed, but reproduced here for clarity with the GetString call:
public class Translator : INotifyPropertyChanged
{
public string this[string text]
{
get
{
return Strings.ResourceManager.GetString(text, Strings.Culture);
}
}
public static Translator Instance { get; } = new Translator();
public event PropertyChangedEventHandler PropertyChanged;
public void Invalidate()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
}
Binding text with:
{i18n:Translate Label_Text}
To trigger the update of languages you just then need to call:
Translator.Instance.Invalidate()
Solution from:
https://forums.xamarin.com/discussion/82458/binding-indexername-and-binding-providevalue-in-xamarin-forms

How is INativeElementView supposed to be implemented?

I was trying to implement ListViewCachingStrategy=RecycleElement in a Xamarin.Forms xaml page and everything displays correctly until I try to scroll. Then I get an exception that says that INativeElementView needs to be implemented. I designed the views in each native platform's ui designer and have a custom renderer for each. This crash does not happen if I use RetainElement instead of RecycleElement. Any ideas or suggestions?
Edit: My view that I created in the designer is called FeeViewCell and if I understand it right that is where INativeElementView is supposed to be implemented (in FeeViewCell.cs). My problem is now that casting to an element returns a System.InvalidCastException saying that the specified cast is not valid. Here is my code implementing INativeElementView:
public Element Element
{
get
{
return this.ToView() as Element;
}
}
I also have this as a question on the Xamarin forum here.
The INativeNativeView interface needs to be implemented on the custom cell class. In my example in iOS I in the custom renderer provides an item that comes from the pcl in my case a FeeCell.
This is part of my custom renderer
public class FeeCellRenderer : ViewCellRenderer
{
public override UIKit.UITableViewCell GetCell(Cell item, UIKit.UITableViewCell reusableCell, UIKit.UITableView tv)
{
var x = (FeeCell)item;
//Do whatever you need to here.
}
}
This is part of my custom ui class:
public partial class FeeViewCell : UITableViewCell, INativeElementView
{
public static readonly NSString Key = new NSString(nameof(FeeViewCell));
Element _element;
public Element Element
{
get { return _element; }
set { _element = value; }
}
}
It also works to do this:
public Element Element
{
get { return new FeeCell(); }
}

Custom control / UIView in Xamarin.iOS with design-time properties

I am creating a user interface for an iOS app and I am looking for the correct way to create a reusable custom control. I got it generally working when running the app, but at design time setting my "exported" properties has no visible effect in the designer. I think I am doing something fundamentally wrong, so perhaps someone could give me guidance
What I am doing:
I have created a subclass of UIControl.
In the constructor I call an Initialize method.
In the Initialize method, I add several subviews and constraints to layout them within my control
Here is some hollowed out code that shows the above:
[Register("RangedValueSelector"), DesignTimeVisible(true)]
public sealed class RangedValueSelector : UIControl
{
public RangedValueSelector(IntPtr p)
: base(p)
{
Initialize();
}
public RangedValueSelector()
{
Initialize();
}
public int HorizontalButtonSpacing
{
get { return _horizontalButtonSpacing; }
set
{
_horizontalButtonSpacing = value;
}
}
[Export("LabelBoxVerticalInset"), Browsable(true)]
public int LabelBoxVerticalInset
{
get
{
return _labelBoxVerticalInset;
}
set
{
_labelBoxVerticalInset = value;
}
}
private void Initialize()
{
//Code that creates and add Subviews
//Code that creates and add the required constraints, some of which should depend on the design time properties
}
}
So the control works perfectly fine if I set the exported properties via the designer, however they do not have an immediate effect in the designer itself.
What is the suggested way of having design-time settable properties that change the constraint values? I would like to avoid having to recreate all the subviews each time someone in the code or in the designer sets a property.
You are missing constructor with RectangleF which is used by designer.
public RangedValueSelector(RectangleF bounds):base(bounds){}
The rest seems to be correct.

Categories

Resources