Xamarin.Forms TimePicker "Time" Binding not settable? - c#

On multiple Views I have TimePickers which I would like to set to a certain time but they always show 00:00.
They are set up like this:
<controls:ExtendedTimePicker VerticalOptions="FillAndExpand"
HorizontalOptions="EndAndExpand"
TextColor="#FFFFFF"
Time="{Binding SaveTime, Mode=TwoWay}" />
Or llike this:
<TimePicker Grid.Row="1"
Grid.Column="1"
Format="HHmm"
Time="{Binding SaveTime, Mode=TwoWay}"
BackgroundColor="#FFFFFF" />
For clarification, ExtendedTimePicker is from the Xlabs.Forms library. As it inherits from the TimePicker, it has the very same issue. Also I've tried every binding mode there is, but all of them got me the same result of 00:00.
In my ViewModels I've tried to set the bound property in the constructor, delay it for some hundred miliseconds, or set it when a specific Message from the MessagingCenter is received. No matter when I set it, the displayed time never changes. The getter of the bound property is accessed after I set the Property to a certain time though.
The Property is defined as:
private TimeSpan _SaveTime;
public TimeSpan SaveTime
{
get { return _SaveTime; }
set { SetProperty(ref _SaveTime, value); }
}
SetProperty leads to this:
public abstract class ViewModelBase : IViewModel, IDisposable
{
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
So, is this a bug? Is there something to fix this? A workaround? Something? Maybe an update?
I am working with:
VisualStudio 2017 15.2
Xamarin 4.5.0
Xamarin.Forms 2.3.4.267

Try to set your Property from your ViewModel by using this code in order to ensure it is set by the UI thread:
Device.BeginInvokeOnMainThread(() => SaveTime = yourTimeSpan);
If, it is not Ok, can you post the line where you set the property?

Okay, shame on me. I had a custom renderer for TimePicker and ExtendedTimePicker which fixed another bug in an older version of Xamarin.Forms where the TimePickerDialog would always be in 12H format no matter what I entered as format. After removing the class from my code, everything works just fine.

Related

How to omit setter of PropertyChanged when the framework draws controls [MAUI.NET]

I have an application in MAUI.NET with MVVM architecture.
In ViewModels I am setting PropertyChanged as most of examples through the web.
In my application the user opens lots of views (that are of type ContentView).
Each time the ContentView is assigned to main area of application and drawn on the monitor, the setters of ViewModels are fired (when they have already binded value).
What I need is to limit this behaviour (firing setters in ViewModels) only to the moment when the user himself/herself click on the checkbox, omitting the moments when the framework just draw the checkbox which have binded value set to true.
In this situation call stack says that external code is firing this.
Anyone have any idea how to deal with this?
edit:
viewmodel:
internal class CheckboxViewModel : BaseViewModel
{
public bool Value
{
get
{
...
}
set
{
//here I compute value and set status to be changed
//this is fired when the user click on checkbox = ok
//but also when checkbox (with binded true value) is drawn on the monitor = problem
}
}
public CheckboxViewModel(XElement item, Registry registry) : base(item, registry)
{
...
}
}
view:
<DataTemplate x:DataType="viewModels:CheckboxViewModel" x:Key="CheckboxDataTemplate">
<CheckBox IsChecked="{Binding Value}" ... />
</DataTemplate>
and my sligthly changed version of INotifyProperty:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new(propertyName));
}
protected virtual void SetPropertyAndNotify<T>(ref T backedProperty, T newValue, [CallerMemberName] string propertyName = null)
{
if (object.Equals(backedProperty, newValue))
return;
backedProperty = newValue;
PropertyChanged?.Invoke(this, new(propertyName));
}
}
situation:
I put a ContentView that in BindedContext has (in some hierarchy) the CheckBoxViewModel and it is drawn. But the setter is fired.

Dynamically update style of control in UWP via a trigger

I want to display the style of a control in a template depending on an enum in the used class. I tried to use this to use the enum in XAML and this to create a trigger. The problem is that I cannot use x:Static in UWP and the trigger is never fired. My workaround does not work either.
My class:
//Namespace Enums
public enum ConnectionState
{
Open,
Closed,
Connecting,
Broken
}
//Namespace Models
public class DatabaseConnection : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ConnectionState _connectionState = ConnectionState.Broken;
public ConnectionState ConnState
{
get => _connectionState;
set
{
if (value != _connectionState)
{
_connectionState = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ConnStateInt));
OnPropertyChanged(nameof(InfoBadgeStyle));
}
}
}
public int ConnStateInt => (int)ConnState;
public Style InfoBadgeStyle
{
get
{
return ConnState switch
{
ConnectionState.Open => (Style)Application.Current.Resources["SuccessIconInfoBadgeStyle"],
ConnectionState.Connecting => (Style)Application.Current.Resources["AttentionIconInfoBadgeStyle"],
ConnectionState.Broken => (Style)Application.Current.Resources["CriticalIconInfoBadgeStyle"],
_ => (Style)Application.Current.Resources["InformationalIconInfoBadgeStyle"],
};
}
}
}
My template:
<Page.Resources>
<DataTemplate x:Key="ConnectionTemplate" x:DataType="models:DatabaseConnection">
<muxc:InfoBadge Style="{x:Bind InfoBadgeStyle}"/>
</DataTemplate>
</Page.Resources>
How can I update the style with a trigger in UWP?
It's unclear why you have both an InfoBadgeStyle property and triggers in XAML but if you want your ChangePropertyActions to be able to set the Style property, you should not set the local Style property like this as it will take precedence:
<muxc:InfoBadge ... Style="{x:Bind InfoBadgeStyle}">
So either remove the InfoBadgeStyle property or remove the triggers.
Also note that x:Bind binds OneTime by default so if you want to react to change notifications you should set the Mode to OneWay: Style="{x:Bind InfoBadgeStyle, Mode=OneWay}"
The problem was that the property was bound only once. Setting Mode to OneWay fixed the problem. Thanks to mm8 for the hint.
<muxc:InfoBadge Style="{x:Bind InfoBadgeStyle, Mode=OneWay}"/>

Calling multiple OnPropertyChanged within the same Setter (or is there a other easy way)

I am trying to keep my question simple and to the point.
At the moment, if I have a property that updates the underlying Model data, and it therefore needs to inform a few other properties that the source has changed, I do it like this:
public Data.MeetingInfo.Meeting Meeting
{
get { return _Meeting; }
set
{
if(value != null)
{
_Meeting = value;
if (_Meeting.IsDirty)
{
_Model.Serialize();
_Meeting.MarkClean();
OnPropertyChanged("Meeting");
OnPropertyChanged("BibleReadingMain");
OnPropertyChanged("BibleReadingClass1");
OnPropertyChanged("BibleReadingClass2");
}
}
}
}
private Data.MeetingInfo.Meeting _Meeting;
As you can see, I added several different OnPropertyChanged method calls. Is this an acceptable way to do it? Or, can the specific properties in the Model inform the View that some of it's source has changed?
I have read about implementing the same OnPropertyChanged features in the Model classes. Thus the XAML will pick it up. But I thought those two parts of the MWWV we not supposed ot know about each other.
The thing is, the other 3 are in disabled controls, but they can be updated from two places on the window. So I don't think I can have two update source triggers can I?
Thank you.
Second attempt at explainign things:
ObservableCollection of Meeting objects. Bound to a ComboBox:
<ComboBox x:Name="comboMeetingWeek" ItemsSource="{Binding Meetings}"
SelectedItem="{Binding Meeting, UpdateSourceTrigger=PropertyChanged}" />
The Meeting object contains several properties. We bind controls on the window with these properties. Example:
<ComboBox x:Name="comboNotes" IsEditable="True"
DataContext="{Binding Meeting}"
Text="{Binding Note, UpdateSourceTrigger=LostFocus}"
ItemsSource="{StaticResource Notes}"/>
I do this for the majority of the controls. So the Meeting property in the view model is kept up to date and then when you select a different meeting it commits it to the model data and displays the new meeting (as previously described).
But, in some places on the window, I have some disabled text boxes. These are associated with properties nested inside the Meeting object. For example:
<TextBox x:Name="textBibleReadingMain" Grid.Column="0" Margin="2" IsEnabled="False"
DataContext="{Binding TFGW.BibleReadingItem.Main}"
Text="{Binding DataContext.BibleReadingMain, ElementName=oclmEditor, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"/>
The parent TabItem already has it's DataContext set to {Binding Meeting}. What we need to display in the text box is:
Meeting (current context).TFGW.BibleReadingItem.Main.Name
This is why I had to do it has I did. For the above text box, this is what I want to allow to happen:
It should display the content of Meeting.TFGW.BibleReadingItem.Main.Name (Meeting already being a bound property).
As you select a different meeting from the dates combo, this text box should update.
If the user selects a name from the DataGrid and the ActiveAstudentAssignmentType combo is set to StudentAssignmentType::BibleReadingMain then I also want to update the text box.
I think what I am getting confused about is when I am supposed to derive my classes from INotifyPropertyChanged. My Model data is the Meeting objects with it's own data. Should all of these be inheriting from INotifyPropertyChanged and raising OnPropertyChanged? At the moment I do not have that implemented anywhere. I tell a lie, the only place I implemented it was for the view model itself:
public class OCLMEditorViewModel : INotifyPropertyChanged
So that is why I had to do it the way I did.
Any clearer?
Based on all the comments and further reasearch ....
One of the answers stated:
Viewmodel is created and wraps model
Viewmodel subscribes to model's PropertyChanged event
Viewmodel is set as view's DataContext, properties are bound etc
View triggers action on viewmodel
Viewmodel calls method on model
Model updates itself
Viewmodel handles model's PropertyChanged and raises its own PropertyChanged in response
View reflects the changes in its bindings, closing the feedback loop
I also read a bit of this (which confused me somewhat) where it stated:
The Model notifies the ViewModel if the data in the underlying data store has changed.
So, the first thing I did was change my Meeting object to derive from INotifyPropertyChanged. In addition, I added new properties for gaining access to deeper data in the Meeting model. Example (stripped down):
public class Meeting : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
#region Bible Reading Name Properties
[XmlIgnore]
public string BibleReadingMainName
{
get { return _TFGW.BibleReadingItem.Main.Name; }
set
{
_TFGW.BibleReadingItem.Main.Name = value;
OnPropertyChanged("BibleReadingMainName");
}
}
[XmlIgnore]
public string BibleReadingClass1Name
{
get { return _TFGW.BibleReadingItem.Class1.Name; }
set
{
_TFGW.BibleReadingItem.Class1.Name = value;
OnPropertyChanged("BibleReadingClass1Name");
}
}
[XmlIgnore]
public string BibleReadingClass2Name
{
get { return _TFGW.BibleReadingItem.Class2.Name; }
set
{
_TFGW.BibleReadingItem.Class2.Name = value;
OnPropertyChanged("BibleReadingClass2Name");
}
}
#endregion
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
In my ViewModel I set it as a listener for PropertyChanged:
_Meeting.PropertyChanged += Meeting_PropertyChanged;
At this point in time, the handler just relays the property that was changed:
private void Meeting_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(e.PropertyName);
}
In my XAML, I adjust my TextBox to work with the new property, and I remove the DataContext reference. So I now have:
<TextBox x:Name="textBibleReadingMain" Grid.Column="0" Margin="2" IsEnabled="False"
Text="{Binding BibleReadingMainName, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
ON the right, where I have the DataGrid, when we click a row and the SelectedStudentItem is updated, we can now do:
private Student _SelectedStudentItem;
public Student SelectedStudentItem
{
get
{
return _SelectedStudentItem;
}
set
{
// We need to remove this item from the previous student history
if (_SelectedStudentItem != null)
_SelectedStudentItem.History.Remove(Meeting.DateMeeting);
_SelectedStudentItem = value;
if (_SelectedStudentItem == null)
return;
_EditStudentButtonClickCommand.RaiseCanExecuteChanged();
_DeleteStudentButtonClickCommand.RaiseCanExecuteChanged();
OnPropertyChanged("SelectedStudentItem");
if (ActiveStudentAssignmentType == StudentAssignmentType.BibleReadingMain)
_Meeting.BibleReadingMainName = _SelectedStudentItem.Name;
else if (ActiveStudentAssignmentType == StudentAssignmentType.BibleReadingClass1)
_Meeting.BibleReadingClass1Name = _SelectedStudentItem.Name;
else if (ActiveStudentAssignmentType == StudentAssignmentType.BibleReadingClass2)
_Meeting.BibleReadingClass2Name = _SelectedStudentItem.Name;
}
Based on the current ActiveStudentAssignmentType value we can directly update the source property. Thus the TextBox will automatically know about it due to the PropertyChange listener.
Thus, the original Meeting property code now looks like this:
public Data.MeetingInfo.Meeting Meeting
{
get { return _Meeting; }
set
{
// Has the existing meeting object changed at all?
if(_Meeting != null && _Meeting.IsDirty)
{
// Yes, so save it
_Model.Serialize();
_Meeting.MarkClean();
}
// Now we can update to new value
if (value != null)
{
_Meeting = value;
OnPropertyChanged("Meeting");
}
}
}
private Data.MeetingInfo.Meeting _Meeting;
All of those extra OnPropertyChanged calls are now obsolete!
The thing I was missing was implementing Notification from the Model to the ViewModel. And then the ViewModel informing the View.

Binding variables to XAML fields

I am Using C# & XAML with Visual Studio 2012
MS changed much of Visual Studio in 2012 that I have not been able to find working solutions on the web. I am new to C#/XAML so I am not familiar with Data Binding, if that is indeed the proper way to proceed.
I need to display variables from the App.xaml.cs file on the MainPage.xaml page. These variables change state every 100-300 msec., so requiring a refresh of the page each time the data changes is probably not a good idea.
Here are code snippets from my project:
App.xaml.cs defines the variables and modifies them in a dispatcherTimer:
namespace OpenGOTO
{
public partial class App : Application
{
public static string DateStrZ = "";
public static string FubarTest { get; set; }
}
}
In MainPage.xaml (which is not always the current window) I have the TextBlock:
<TextBlock x:Name="UTC_Data" Text="2012-08-01 03:29:07Z" Padding="5" Style="{StaticResource TextBlockStyle1}" />
In MainPage.xaml.cs I have routines that are called by a dispatcherTimer that updates the fields:
public void SetFieldsTick()
{
UTC_Data.Text = App.DateStrZ;
}
If I change this to
public static void SetFieldsTick()
so that I can call it from the App.xaml.cs dispatcherTimer, I get the error message:
An object reference is required for the non-static field, method, or property 'OpenGOTO.MainPage.UTC_Data'
How do I either:
Bind the data to the field (and will it automatically update without needing to refresh the whole window?)
Create the correct references so that the dispatcherTimer in App.xaml.cs can call a routine in MainPage.xaml.cs that sets the fields in the XAML page.
To use a Binding that gets updates from the data you need a few things:
A property to bind to
Some implementation of change notification, usually using INotifyPropertyChanged or a DependencyProperty
An object instance on which the property is declared
You currently have none of these. Start by making an object that implements INotifyPropertyChanged with a property to store your data:
public class MyBindableObject : INotifyPropertyChanged
{
private string _dateStr;
public string DateStr
{
get { return _dateStr; }
set
{
if (_dateStr == value)
return;
_dateStr = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("DateStr"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
You can then expose a static instance of this from your App class and make updates to this instance whenever new data comes in:
private static MyBindableObject _bindingContainer = new MyBindableObject();
public static MyBindableObject BindingContainer
{
get { return _bindingContainer; }
}
public static void SetNewData()
{
// use this anywhere to update the value
App.BindingContainer.DateStr = "<Your New Value>";
}
Now you have everything you need for a Binding and you just need to expose it to your page. You can do this by setting the DataContext of your page, which is the default binding source:
public MainPage()
{
this.InitializeComponent();
DataContext = App.BindingContainer;
}
Now you can bind your TextBlock:
<TextBlock x:Name="UTC_Data"
Text="{Binding Path=DateStr}"
Padding="5" Style="{StaticResource TextBlockStyle1}"/>
Why can't you just call the UTC_Data from App.xaml.cs?
For example:
((MainPage) rootFrame.Content).UTC_Data.Text = DateStrZ;
Of course UTC_Data won't be accessible until you change it like this:
<TextBlock x:FieldModifier="public" x:Name="UTC_Data" Text="2012-08-01 03:29:07Z" Padding="5" Style="{StaticResource TextBlockStyle1}"/>

WPF Binding doesn't work if the View Model Property is set trough Reflection

I'm trying to build up a page that will show a progress bar until the data is successfully downloaded from a server.
For this i use a Generic data downloader that will simply populate the Model's properties and set the IsLoading property to true and/or false
The View models look like:
public class GenericPageModel: GenericModel
{
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set
{
_isLoading = value;
OnPropertyChanged("IsLoading");
}
}
}
public class GenericModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
The GenericPageModel is used in a XAML page as a Model and the IsLoading property is used as bellow:
<Grid>
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="boolToVis"/>
</Grid.Resources>
<ProgressBar Height="25" Margin="5"
VerticalAlignment="Center" HorizontalAlignment="Stretch"
Visibility="{Binding IsLoading, Converter={StaticResource boolToVis}}"
IsIndeterminate="True"
/>
</Grid>
The generic data downloader:
...
// Model that it's calling this
public object Model
{ get; set; }
private string _loadingProperty;
...
void _bw_DoWork(object sender, DoWorkEventArgs e)
{
// Set the is loading property
if (null != _loadingProperty)
{
//((Model as GenericModel).Owner as GenericPageModel).IsLoading = true; // Works!!
Model.GetType().GetProperty(_loadingProperty).SetValue(Model, true, null); // Doesn't work
}
}
If i explicitly cast Model to the GenericPageModel and set the IsLoading to true, everything works just fine (see the commented line)
If i use reflection to set the Value of the property, the IsLoading setter is hit correctly, the OnPropertyChanged method is called ok, but the UI doesn't update
Is there something extra that needs to be done when setting a property trough reflection? I 'm guessing the Events aren't raised properly but i can't figure out what to do.
Solved there was an extra model inserted before the downloader call, the line should've said:
object Owner = Model.GetType().GetProperty("Owner").GetValue(Model, null);
Model.GetType().GetProperty(_loadingProperty).SetValue(Owner, true, null);
The following lines
((Model as GenericModel).Owner as GenericPageModel).IsLoading = true; // Works!!
Model.GetType().GetProperty(_loadingProperty).SetValue(Model, true, null); // Doesn't work
are not equivalent. The first affects the IsLoading property on the Model.Owner object, the second on the Model object itself.
Note: your ViewModel base class really shouldn't be called GenericModel as it's the ViewModel, not the Model.

Categories

Resources