My control has 5 dependency properties. I set them in XAML like this:
<MyControl Prop1="1" Prop2="2" Prop3="3" Prop4="4" />
As you can see, I am only setting 4 of the 5 properties in XAML.
What I need to find is some mechanism to indicate that all the properties set in XAML have been processed. With this event, I can run my SetItAllUp() method.
Option 1. Use the DP setter
FAIL: Not an option because I cannot call SetItAllUp() but one time. This also has the side effect of activating based on the ordinal declaration of each DP in the XAML. If there is some type of chaining or dependency between my properties, this undermines it.
Option 2. Use the DP setter, and test all values are set
FAIL: Not an option because sometimes certain DP values are optional - let's just pretend that the logic necessary to determine if optional values are properly set or not is too complex to implement this solution for now, please.
Option 3. Use MyControl.Loaded
FAIL: Not an option because this fires too early. In fact, every event I can see fires too early. It is almost as if the object is created and then something under the hood starts to set the DP values based on the declarations.
Update! Loaded is the solution. My question was flawed.
There's some event or something, right?
// Thanks
Loaded works fine. Sorry for the confusion.
I tested this class:
public class MyPath : Path
{
public MyPath()
{
Loaded += MyPath_Loaded;
}
void MyPath_Loaded(object sender, RoutedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Loaded");
}
public int Test1
{
get { return (int)GetValue(Test1Property); }
set
{
SetValue(Test1Property, value);
System.Diagnostics.Debug.WriteLine("Test1");
}
}
public static readonly DependencyProperty Test1Property =
DependencyProperty.Register("Test1", typeof(int), typeof(MyPath),
new PropertyMetadata(DependencyProperty.UnsetValue, null));
public int Test2
{
get { return (int)GetValue(Test2Property); }
set
{
SetValue(Test2Property, value);
System.Diagnostics.Debug.WriteLine("Test2");
}
}
public static readonly DependencyProperty Test2Property =
DependencyProperty.Register("Test2", typeof(int), typeof(MyPath),
new PropertyMetadata(DependencyProperty.UnsetValue, null));
public int Test3
{
get { return (int)GetValue(Test3Property); }
set
{
SetValue(Test3Property, value);
System.Diagnostics.Debug.WriteLine("Test3");
}
}
public static readonly DependencyProperty Test3Property =
DependencyProperty.Register("Test3", typeof(int), typeof(MyPath),
new PropertyMetadata(DependencyProperty.UnsetValue, null));
public int Test4
{
get { return (int)GetValue(Test4Property); }
set
{
SetValue(Test4Property, value);
System.Diagnostics.Debug.WriteLine("Test4");
}
}
public static readonly DependencyProperty Test4Property =
DependencyProperty.Register("Test4", typeof(int), typeof(MyPath),
new PropertyMetadata(DependencyProperty.UnsetValue, null));
public int Test5
{
get { return (int)GetValue(Test5Property); }
set
{
SetValue(Test5Property, value);
System.Diagnostics.Debug.WriteLine("Test5");
}
}
public static readonly DependencyProperty Test5Property =
DependencyProperty.Register("Test5", typeof(int), typeof(MyPath),
new PropertyMetadata(DependencyProperty.UnsetValue, null));
}
With this XAML:
<Grid Background="{StaticResource ApplicationPageBackgroundBrush}">
<local:MyPath Test1="1" Test2="2" Test3="3" Test4="4" />
</Grid>
And got this Trace:
Test1
Test2
Test3
Test4
Loaded
And it turns out Loaded works just fine.
My previous testing must have had some other factor.
My simplified test shows Loaded seems perfect.
Related
I'm trying to create a custom control that has a header and a footer and body. The idea is that the body of the report is a custom stack panel control that will allow the user to indicate page orientation and grouping. I created a dependency property on the custom UC to accept an IList of the custom stack panel. What I am trying to do is bind to one of the stack panels in the list. But for some reason the binding is not working.
The ReportPage:
public class ReportPage : StackPanel
{
//Nothing right now but will eventually include controls for page orientation and size (8.5x11, 11x17, etc.)
}
The UserControl code behind:
public partial class Report : UserControl, INotifyPropertyChanged
{
public Report()
{
ReportPages = new List<ReportPage>();
}
public static readonly DependencyProperty =
DependencyProperty.Register("ReportPages", typeof(IList), typeof(Report));
public IList ReportPages
{
get => (IList)GetValue(ReportPagesProperty);
set
{
SetValue(ReportPagesProperty, value);
ActivePage = value[0];
OnPropertyChanged(nameof(ActivePage));
}
}
private ReportPage _activePage;
public ReportPage ActivePage
{
get => _activePage;
set
{
_activePage = value;
OnPropertyChanged(nameof(ActivePage));
}
{
}
The UserControl xaml:
<Grid>
<!--Some xaml for the header and footer.-->
<ContentControl Content="{Binding ActivePage, RelativeSource={RelativeSource, FindAncestor, AncestorType=local:Report}}"/>
</Grid>
Here is how I am consuming the custom control. This should, in my mind at least, make three "pages" which I can toggle between using a button control that I didn't share.
<reportEngine:Report>
<reportEngine:Report.ReportPages>
<reportEngine:ReportPage>
<TextBlock>This is Page 1</TextBlock>
</reportEngine:ReportPage>
<reportEngine:ReportPage>
<TextBlock>This is Page 2</TextBlock>
</reportEngine:ReportPage>
<reportEngine:ReportPage>
<TextBlock>This is Page 3</TextBlock>
</reportEngine:ReportPage>
</reportEngine:Report.ReportPages>
</reportEngine:Report>
Any Ideas why the binding isn't working?
So I at least found a quick work around. I utilized the Collection Changed Event handler pattern from this answer and modified it for static dependency properties. Then, to get the values from the collection bound to the dependency property I create a static instance of the Report object in the constructor and use that to pass various values back to the object from the collection. Something like this:
public partial class Report : UserControl, INotifyPropertyChanged
{
private static Report _thisReport;
public Report()
{
InitializeComponent();
ReportPages = new ObservableCollection<ReportPage>();
_thisReport = this;
}
public static readonly DependencyProperty ReportPagesProperty =
DependencyProperty.Register("ReportPages", typeof(IList), typeof(Report), new FrameworkPropertyMetadata(ReportPagesChanged));
public IList ReportPages
{
get => (IList)GetValue(ReportPagesProperty);
set
{
SetValue(ReportPagesProperty, value);
//Update some other properties associated with the control (Total Page Numbers, etc.)
}
}
private static void ReportPagesChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
var newColl = (INotifyCollectionChanged)eventArgs.NewValue;
if (newColl != null)
newColl.CollectionChanged += ReportPages_CollectionChanged;
var oldColl = (INotifyCollectionChanged)eventArgs.OldValue;
if (oldColl != null)
oldColl.CollectionChanged -= ReportPages_CollectionChanged;
}
private static void ReportPages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs eventArgs)
{
var newPages = (IList<ReportPage>) sender;
//Updates properties of the Report control.
_thisReport.ActivePage = newPages[0];
_thisReport.TotalPageNumber = newPages.Count;
}
}
Whether this is "correct" or not I couldn't say, but it works. If someone has a better answer I will change the answer.
I've got a custom MyCachedImage that inherits from FFImageLoading.Forms.CachedImage, which is used in a ListView to display images.
The source of this image is composed by 2 properties: a custom object as entity and an integer as size.
Let's say if entity is a "city" object and size is 10 then the image source will be "http://..../city/10/image.png"
Image source must be setted only when both properties are valorized.
So, my answer is, how and when create the source url?
MyCachedImage.vb
public class MyCachedImage : CachedImage
{
public static readonly BindableProperty EntityProperty =
BindableProperty.Create(nameof(Entity), typeof(MyObject), typeof(MyCachedImage));
public MyObject Entity
{
get { return (MyObject)GetValue(EntityProperty); }
set { SetValue(EntityProperty, value); }
}
public static readonly BindableProperty SizeProperty =
BindableProperty.Create(nameof(Size), typeof(int), typeof(MyCachedImage), defaultValue: 0);
public int Size
{
get { return (int)GetValue(SizeProperty); }
set { SetValue(SizeProperty, value); }
}
public MyCachedImage()
{
??? set source here?
}
protected override void OnBindingContextChanged()
{
??? set source here?
}
}
MyPage.xaml
<ListView ....>
....
<control:MyCachedImage Size="10"
Entity="{Binding MyObject}"
WidthRequest="40"
HeightRequest="40" />
....
</ListView>
I was wondering on when create that string and I found the right solution.
The OnBindingContextChanged is called when all properties are setted, so:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (_source == string.Empty)
{
Source = Helpers.ImageHelper.UriFromEntity(Entity, ImageSize);
}
}
I am using Strings.resx, Strings.de.resx, etc. to localize Xamarin.Forms app.
I need to be able to change interface language at run time, and it (allmost) works.
Xamarin generates static class Strings in namespace MyProject.Resources from resource files, and I use those values to display strings on UI.
When doing it from code, it works flawlessly:
await DisplayAlert(Strings.lblConfirmDelete, Strings.lblDeleteMessage, Strings.lblOK, Strings.lblCancel));
Problem is - not all attributes defined this way from XAML are updated when I change UI culture during runtime.
Buttons, Labels, Entry properties (Placeholder etc.) change as they should, but PageTitle, Toolbaritems, and some other properties remain in previous language.
I presume that some of these are populated when Page is first created, and are not updated on culture (and UI culture) change.
So, basically, I need a way to combine {DynamicResource ...} with values from resources.
I know that DynamicResource is ment to be used with Resource dictionary, but that is not a good way to store language translations for localization.
I tried
Text="{DynamicResource {x:Static lr:Strings.lblAddNew}}"
also not working.
Is there a way of refreshing page dynamicaly?
I also tried calling
global::Xamarin.Forms.Xaml.Extensions.LoadFromXaml(this, typeof(MainListPage));
from Appearing event for that page, but that also does not work.
Any ideas?
Part of XAML file
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyProject.View"
xmlns:rs="clr-namespace:MMPI"
x:Class="MyProject.MainListPage"
xmlns:lr="clr-namespace:MyProject.Resources"
Title="{x:Static lr:Strings.appName}"
>
<ContentPage.ToolbarItems>
<ToolbarItem
Name="New"
Order="Primary"
Priority="0"
Text="{x:Static lr:Strings.lblAddNew}"
Clicked="New_Clicked"
>
When i encountered that challenge in a project I resolved it by using a simple class ResourceLoader and making use of INotifyPropertyChanged.
You can access the Instanceproperty from anywhere and change the culture. All String that are bound to the index would update.
The ResourceManager instance injected into the constructor must be set up appropriately.
public class ResourceLoader : INotifyPropertyChanged
{
private readonly ResourceManager manager;
private CultureInfo cultureInfo;
public ResourceLoader(ResourceManager resourceManager)
{
this.manager = resourceManager;
Instance = this;
this.cultureInfo = CultureInfo.CurrentUICulture;
}
public static ResourceLoader Instance { get; private set; }
public string GetString(string resourceName)
{
string stringRes = this.manager.GetString(resourceName, this.cultureInfo);
return stringRes;
}
public string this[string key] => this.GetString(key);
public void SetCultureInfo(CultureInfo cultureInfo)
{
this.cultureInfo = cultureInfo;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
To display the localized strings in your application you need to bind through the indexer like so:
<Label Text="{Binding [Test], Source={x:Static ResourceLoader.Instance}}" />
Since it is now bound it should update when you call ResourceLoader.SetCultureInfo because the Item[] 'PropertyName' is causing bound controls to re-fetch the values to their bound keys.
Update
I just tested it if i was talking bogus and for some reason the property changed didn't work. I've added a different approach below, which is close to what i'm using in production i urge you to add some kind of weak reference 'caching' instead of the simple list holding all the string resources (otherwise they will be kept forever)
I'm keeping above for reference.
public class ResourceLoader
{
public ResourceLoader(ResourceManager resourceManager)
{
this.manager = resourceManager;
Instance = this;
this.cultureInfo = CultureInfo.CurrentUICulture;
}
private readonly ResourceManager manager;
private CultureInfo cultureInfo;
private readonly List<StringResource> resources = new List<StringResource>();
public static ResourceLoader Instance { get; private set; }
public StringResource this[string key] {
get { return this.GetString(key); }
}
public StringResource GetString(string resourceName)
{
string stringRes = this.manager.GetString(resourceName, this.cultureInfo);
var stringResource = new StringResource(resourceName, stringRes);
this.resources.Add(stringResource);
return stringResource;
}
public void SetCultureInfo(CultureInfo cultureInfo)
{
this.cultureInfo = cultureInfo;
foreach (StringResource stringResource in this.resources) {
stringResource.Value = this.manager.GetString(stringResource.Key, cultureInfo);
}
}
}
StringResource:
public class StringResource : INotifyPropertyChanged
{
public StringResource(string key, string value)
{
this.Key = key;
this.Value = value;
}
private string value;
public string Key { get; }
public string Value {
get { return this.value; }
set {
this.value = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML Binding
<Label Text="{Binding [ResourceKey].Value, Mode=OneWay, Source={x:Static local:ResourceLoader.Instance}}"
/>
Update 2
Came across this link where they implemented it similarly to my first approach. Maybe you can give it a try.
Update 3
Fixed the first approach. Both are working now. What was needed was this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null)); instead of this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Item[]));
I solved it very similar to #woelliJ . I just wanted to have key as strongly types from static class and binding should be in code behind.
ITranslationService is singleton from static variable. It is very close like #woelliJ .
[ContentProperty("Text")]
public sealed class TranslateExtension : IMarkupExtension<BindingBase>
{
private readonly ITranslationService? _translationService;
public TranslateExtension()
{
_translationService = Mobile.App.TranslationService;
}
public string? Text { get; set; }
public BindingBase ProvideValue(IServiceProvider serviceProvider)
{
var translationItem = _translationService[Text];
var binding = new Binding
{
Mode = BindingMode.OneWay,
Path = $"Value",
Source = translationItem,
};
return binding;
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
}
}
[AddINotifyPropertyChangedInterface]
public class TranslationItem
{
public string? Key { get; set; }
public string? Value { get; set; }
}
Then label would be like this
<Label FontSize="Title" Text="{services:Translate Text={x:Static models:M.AboutTestInfoTitle}}" />
I'm making app with using Xamarin.forms.
I set BindableProperty of integer.
But it's setter never called and not even set from xaml.
<... SelectedIndex="{Binding myValue}">
Is there anything I did wrong?
Thanks.
public class CircleSegmentControl : StackLayout
{
public static readonly BindableProperty SegmentInitialIndexProperty = BindableProperty.Create("SelectedIndex", typeof(int), typeof(CircleSegmentControl), 0);
public int SelectedIndex {
set{
Debug.WriteLine("setter");
SetValue(SegmentInitialIndexProperty, value);
SelectIndex(value);
}
get{
Debug.WriteLine("getter");
return (int)GetValue(SegmentInitialIndexProperty);
}
}
...
}
Apparently the same applies as for WPF dependency properties. You must not call anything else than GetValue and SetValue in the get and set methods of the property wrapper:
public int SelectedIndex
{
get { return (int)GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
To get notified about property value changes (to call your SelectIndex method), you should register a propertyChanged delegate with another overload of BindableProperty.Create.
I want to create custom text box with attached property for Windows Store app. I am following this solution. Now it uses hard coded value as property value but I want to set value using binding, but it's not working. I tried to search a lot but didn't helped me any solution.
The exception details is like this
An exception of type 'Windows.UI.Xaml.Markup.XamlParseException'
occurred in CustomTextBox.exe but was not handled in user code
WinRT information: Failed to assign to property
'CustomTextBox.Input.Type'.
MainPage.xaml
<!-- local:Input.Type="Email" works -->
<!-- local:Input.Type="{Binding SelectedTextboxInputType}" not working -->
<TextBox x:Name="txt" local:Input.Type="{Binding SelectedTextboxInputType}" Height="30" Width="1000" />
<ComboBox x:Name="cmb" ItemsSource="{Binding TextboxInputTypeList}" SelectedItem="{Binding SelectedTextboxInputType}" Height="30" Width="200"
Margin="451,211,715,527" />
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataContext = new ViewModel();
}
}
Input.cs
//InputType is enum
public static InputType GetType(DependencyObject obj)
{
return (InputType)obj.GetValue(TypeProperty);
}
public static void SetType(DependencyObject obj, InputType value)
{
obj.SetValue(TypeProperty, value);
}
public static readonly DependencyProperty TypeProperty =
DependencyProperty.RegisterAttached("Type", typeof(InputType), typeof(TextBox), new PropertyMetadata(default(InputType), OnTypeChanged));
private static void OnTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is InputType)
{
var textBox = (TextBox)d;
var Type = (InputType)e.NewValue;
if (Type == InputType.Email || Type == InputType.URL)
{
textBox.LostFocus += OnLostFocus;
}
else
{
textBox.TextChanged += OnTextChanged;
}
}
}
ViewModel.cs
public class ViewModel : BindableBase
{
public ViewModel()
{
TextboxInputTypeList = Enum.GetValues(typeof(InputType)).Cast<InputType>();
}
private InputType _SelectedTextboxInputType = InputType.Currency;
public InputType SelectedTextboxInputType
{
get { return _SelectedTextboxInputType; }
set { this.SetProperty(ref this._SelectedTextboxInputType, value); }
}
private IEnumerable<InputType> _TextboxInputTypeList;
public IEnumerable<InputType> TextboxInputTypeList
{
get { return _TextboxInputTypeList; }
set { this.SetProperty(ref this._TextboxInputTypeList, value); }
}
}
This is a pretty common mistake. The problem is, binding targets cannot be CLR properties in XAML. It's just the rules. A binding source can be a CLR property, just fine. The targets simply must be dependency properties.
We all get the error! :)
I describe the whole thing here: http://blogs.msdn.com/b/jerrynixon/archive/2013/07/02/walkthrough-two-way-binding-inside-a-xaml-user-control.aspx
Best of luck.
Incorrect
public static readonly DependencyProperty TypeProperty =
DependencyProperty.RegisterAttached("Type", typeof(InputType), typeof(TextBox), new PropertyMetadata(default(InputType), OnTypeChanged));
Correct
public static readonly DependencyProperty TypeProperty =
DependencyProperty.RegisterAttached("Type", typeof(InputType), typeof(Input), new PropertyMetadata(default(InputType), OnTypeChanged));