Dynamically update style of control in UWP via a trigger - c#

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}"/>

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.

Xamarin.Forms TimePicker "Time" Binding not settable?

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.

Gridview doesn't change when data changes

I've added an observable data and bound it to my data grid as follows.
private ObservableCollection<Order> _allOrders;
public ObservableCollection<Order> AllOrders
{
get { return _allOrders;}
set { _allOrders = value; OnPropertyChanged(); }
}
public Presenter() { _allOrders = new ObservableCollection<Order>(...); }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] String propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
When I set breakpoint on the event that is supposed to filter the data, I set the property AllOrder to null. I can verify using the watch that it's set to that. However, the view isn't updated, so I'm guessing that I forgot something. The view model class Presenter implements INotifyPropertyChanged interface, of course.
What's missing?
Edit
The XAML code for the grid looks as follows.
<DataGrid x:Name="dataGrid"
ItemsSource="{Binding AllOrders}"
AutoGeneratingColumn="DataGrid_OnAutoGeneratingColumn" ...>
Assuming that you set DataContext accordingly and AllOrders binding works initially if you want to filter items in the UI, without change collection, it's much easier when you use ListCollectionView with a Filter. WPF does not bind directly to collection but to a view - MSDN.
private readonly ObservableCollection<Order> _allOrders;
private readonly ListCollectionView _filteredOrders;
public ICollectionView FilteredOrders
{
get { return _filteredOrders; }
}
public Presenter()
{
_allOrders = new ObservableCollection<Order>(...);
_filteredOrders = new ListCollectionView(_allOrders);
_filteredOrders.Filter = o => ((Order)o).Active;
}
and in XAML
<DataGrid ... ItemsSource="{Binding FilteredOrders}">
when you want to manually refresh UI just call Refresh
_filteredOrders.Refresh();
Apart from that nothing changes in the view model. You still add/remove items to _allItems and changes should be picked up automatically by UI
Do you set the property AllOrders only in the constructor? If so, then do not set the field _allOrders but the property AllOrders. If you set the field then notification is never raised.

INotifyPropertyChanged issue with combobox

See my CustomView window below
When I select project from the combobox, the client associated with that project should automatically displayed there.
In Combobox's selection changed event, I did so far
private string client
{
get
{
return ClientText.Text;
}
set
{
ClientText.Text = value;
}
}
public Harvest_Project projectClass
{
set
{
ProjectText.Text = value.ToString();
Harvest_Project proj = (Harvest_Project)ProjectText.Text; // shows error here. casting is not possible. What can I do here?
this.client = Globals._globalController.harvestManager.getClientEntriesThroughId(proj._client_id)._name;
PropertyChanged(this, new PropertyChangedEventArgs("client"));
}
}
public int project
{
get
{
return int.Parse(ProjectText.Text);
}
set
{
ProjectText.Text = value.ToString();
}
}
private void ProjectComboBoxChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is ComboBoxItem)
{
ComboBoxItem item = (ComboBoxItem)sender;
}
}
In xaml I used binding like this,
<ComboBox x:Name="ProjectText" SelectionChanged="ProjectComboBoxChanged" ItemsSource="{Binding Path=projectList}" SelectedValuePath="_id" DisplayMemberPath="_name" SelectedItem="{Binding ProjectComboBoxChanged, Mode=OneWayToSource}" Background="Yellow" BorderThickness="0" Width="66"/>
In your event handler ProjectComboBoxChanged(object sender, SelectionChangedEventArgs e), sender is of type ComboBox and not ComboBoxItem, hence your if statement is always false.
e.AddedItems[0] will give you your desired ComboBoxItem. Make sure you check the count first.
Also, if all you want to do is to set the Text, you don't need to have the client property.
"client" is a property, it should be public.
Then PropertyChanged should be raised in the setter, so anytime you change client, the UI will know.
About the combo, SelectedItem should bind to a property, not a method. The property could be "client", but another property could be clearer.
In the setter of this property, you'll have the liberty to fix the new value of the "client" property.
And last, since you are using a binding for selectedItem, I see no reason to use the event selectionChanged. Use binding or event, not both.
Hope it helps ;)

Binding to a ViewModel's Child Class Properties

I'm struggling to be able to bind a StatusBarItem content element in my view to a subclasses property in my ViewModel, I'm using the MVVM-Light framework/
ViewModel:
public class PageMainViewModel : ViewModelBase
{
LoggedOnUserInfo UserInfo;
public LoggedOnUser UserInfo
{
set
{
_UserInfo = value;
RaisePropertyChanged("UserInfo");
}
}
}
For full clarity the LoggedOnUser Class is defined as follows
public class LoggedOnUser : INotifyPropertyChanged
{
private string _Initials;
public event PropertyChangedEventHandler PropertyChanged;
public LoggedOnUser()
{
}
[DataMember]
public string Initials
{
get { return _Initials; }
set
{
_Initials = value;
OnPropertyChanged("Initials");
}
}
protected void OnPropertyChanged(string propValue)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propValue));
}
}
}
My Views DataContext is being set and is working as I am able to see other bindings working, but my attempts to bind to UserInfo.Initials property in my XAML are producing an empty result.
XAML:
<StatusBarItem Grid.Column="0" Content="{Binding UserInfo.Initials}" Margin="5,0,0,0" VerticalAlignment="Center" Focusable="False" />
The UserInfo property is set after the viewModel is created due to several factors but I thought with my propertychanged events this would be ok.
Any Advice on this would be greatly appreciated.
You do not appear to have a getter on UserInfo, the binding will be out of luck.
(Also check for binding errors when having trouble with bindings, they probably will tell you about all their problems)
add the getter to your userinfo
public class PageMainViewModel : ViewModelBase
{
LoggedOnUserInfo UserInfo;
public LoggedOnUser UserInfo
{
get {return _UserInfo;}
set
{
_UserInfo = value;
RaisePropertyChanged("UserInfo");
}
}
}
and like H.B. said - check your output window for binding errors
I am not quite sure why you have the initials_ attribute bound inside the UserInfo_ attribute.
You can't access the initials_ attribute without a getter of the UserInfo_ attribute.
I would suggest to bind to the latter separately.

Categories

Resources