Setting or modifying ThemeResource in code - xaml

My questions is very specific to ThemeResources in a Windows 10 Store App. Unfortunately several things available in "classic" WPF are different or not available here.
What I am trying to achieve for lots of ui elements:
Allow the user to use the system's accent color (in XAML this would be {ThemeResource SystemAccentColor} as value.)
Allow the user to use a custom/fixed color instead. (I could override the SystemAccentColor key in the resourcedictionary)
Allow to switch between system accent and custom color at runtime (I could bind against a color instead of using a resource)
But I have not found a good solution to achieve all of this. If I have my own resource dictionary with the custom color, I won't get rid of it when the user would like to switch back to the system's accent color.
And using a property I am binding against has the drawback that I do not realize if the user changes the accent color in the system settings while the app is running - using the {ThemeResource} markup it does.
Any ideas how to get this done properly?
If it would be possible to set the ThemeResource from code I could write some behavior for this, but it seems not to be available.

In Windows 10, the name "Accent Color" is changed to "SystemControlHighlightAccentBrush", and it's a ThemeResource
Example using it
<TextBlock Foreground="{ThemeResource SystemControlHighlightAccentBrush}"
Text="This is a sample text" />
To override it, simply change value of it in App.xaml
<Application.Resources>
<SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="Orange" />
</Application.Resources>
To switch, it's a little bit more difficult
First, you need to set all the color for each theme in App.xaml
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="Orange" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="Green" />
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="Blue" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Application.Resources>
Then, in the page or in code behind, you set the corresponding theme
<TextBlock x:Name="TestTextBlock"
Foreground="{ThemeResource SystemControlHighlightAccentBrush}"
RequestedTheme="Dark"
Text="This is a sample text" />
or in C#
TestTextBlock.RequestedTheme = ElementTheme.Dark;

There is a way how to kinda set ThemeResource in code... I've tested it only on W10 Creators Update so it may not work on older versions, but you can create your own resource that is referencing the original ThemeResource you want to use and then use this resource:
XAML:
<SolidColorBrush x:Key="MyBorderBrush" Color="{ThemeResource SystemAccentColor}"/>
C#:
element.BorderBrush = (SolidColorBrush)Resources["MyBorderBrush"];
The border color of element will be the same as the Accent Color selected in Windows Settings and it will change even when your app is running and user changes it.

I'm using this to set newAccentColor, until I can find a way to do it without toggling the theme. This updates all the derived brushes based on accent color:
Application.Current.Resources["SystemAccentColor"] = newAccentColor;
if (Window.Current.Content is FrameworkElement fe)
{
var requestedTheme = fe.RequestedTheme;
fe.RequestedTheme = fe.RequestedTheme == ElementTheme.Light ? ElementTheme.Dark : ElementTheme.Light;
fe.RequestedTheme = requestedTheme;
}

Once I've also faced the same problem and I also haven't found a way to programatically change ThemeResource so that it will change along with phone's theme. Nevertheless there is a way to achieve what you want, but it's cumbersome and may need a lot of work when you want to implement this to many controls.
The basic idea is to use VisualStates to change from/to ThemeResource - the states are defined in xaml so this will work with ThemeResources. Then in code you can invoke the change back to phone's theme value. Here below is the sample button changing to theme's/user's color.
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Name="ColorBtn" Content="Change users color to green rom red"/>
<local:ExtendedButton x:Name="UserBtn" Content="Change to user's theme" UserBackground="Red">
<local:ExtendedButton.Style>
<Style TargetType="local:ExtendedButton">
<!--default style's setters-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ExtendedButton">
<Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="ThemeColor">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="RootGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemColorControlAccentColor}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="UserColor"/>
</VisualStateGroup>
<!--rest of default visual states-->
</VisualStateManager.VisualStateGroups>
<ContentPresenter x:Name="ContentPresenter" AutomationProperties.AccessibilityView="Raw" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:ExtendedButton.Style>
</local:ExtendedButton>
</StackPanel>
and code behind:
public class ExtendedButton : Button
{
public SolidColorBrush UserBackground
{
get { return (SolidColorBrush)GetValue(UserBackgroundProperty); }
set { SetValue(UserBackgroundProperty, value); }
}
public static readonly DependencyProperty UserBackgroundProperty =
DependencyProperty.Register("UserBackground", typeof(SolidColorBrush), typeof(ExtendedButton),
new PropertyMetadata(new SolidColorBrush(Colors.Red), (s, e) =>
{ if ((s as ExtendedButton).IsUserTheme) (s as ExtendedButton).Background = e.NewValue as SolidColorBrush; }));
// we need some property to indicate if to use user's theme or phone's
public bool IsUserTheme
{
get { return (bool)GetValue(IsUserThemeProperty); }
set { SetValue(IsUserThemeProperty, value); }
}
public static readonly DependencyProperty IsUserThemeProperty =
DependencyProperty.Register("IsUserTheme", typeof(bool), typeof(ExtendedButton), new PropertyMetadata(false, (s, e) =>
{
if ((bool)e.NewValue)
{
VisualStateManager.GoToState((s as ExtendedButton), "UserColor", false);
(s as ExtendedButton).Background = (s as ExtendedButton).UserBackground;
}
else VisualStateManager.GoToState((s as ExtendedButton), "ThemeColor", false);
}));
}
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
Random random = new Random();
UserBtn.Click += (s, e) => UserBtn.IsUserTheme = !UserBtn.IsUserTheme; ;
ColorBtn.Click += (s, e) => UserBtn.UserBackground = new SolidColorBrush(Color.FromArgb(0xFF, (byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255)));
}
}
It's a long way above just to change one color, but should work and maybe will give you an idea. Those are also DependencyProperties so you can use binding if needed.

I have a solution based on a couple of 'helper' classes. First up is just a container object with a DependencyProperty Value that can be bound or set to {ThemeResource …}:
public class DependencyObjectReference<T> : DependencyObject where T : DependencyObject
{
#region Properties
public T Value
{
get { return (T)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
#endregion
#region Static Data
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value),
typeof(T),
typeof(DependencyObjectReference<T>),
new PropertyMetadata(default(T)));
#endregion
}
Next is the meat of the solution: a 'selector' that contains a bunch of references and selects from them based on an index:
[ContentProperty(Name = nameof(References))]
public class DependencyObjectSelector<T> : DependencyObject where T : DependencyObject
{
#region Constructors
public DependencyObjectSelector()
{
References = new DependencyObjectCollection();
}
#endregion
#region Properties
public DependencyObjectCollection References
{
get { return (DependencyObjectCollection)GetValue(ReferencesProperty); }
set { SetValue(ReferencesProperty, value); }
}
public Int32 SelectedIndex
{
get { return (Int32)GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
public T SelectedObject
{
get { return (T)GetValue(SelectedObjectProperty); }
set { SetValue(SelectedObjectProperty, value); }
}
#endregion
#region Event Handlers
private void Evt_OnVectorChangedReferences(IObservableVector<DependencyObject> sender, IVectorChangedEventArgs args)
{
UpdateSelectedObject();
}
#endregion
#region Private Implementation Methods
private void UpdateSelectedObject()
{
if (
References != null
&&
SelectedIndex >= 0
&&
SelectedIndex < References.Count
&&
References[SelectedIndex] is DependencyObjectReference<T>
)
{
BindingOperations.SetBinding
(
this,
SelectedObjectProperty,
new Binding
{
Source = References[SelectedIndex],
Path = new PropertyPath(nameof(DependencyObjectReference<T>.Value))
}
);
}
else
{
ClearValue(SelectedObjectProperty);
}
}
private void OnReferencesPropertyChanged(DependencyObjectCollection oldValue, DependencyObjectCollection newValue)
{
if (oldValue != null)
oldValue.VectorChanged -= Evt_OnVectorChangedReferences;
if (newValue != null)
newValue.VectorChanged += Evt_OnVectorChangedReferences;
}
private static void ReferencesPropertyChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs args)
{
DependencyObjectSelector<T> _this = (DependencyObjectSelector<T>)dobj;
_this.OnReferencesPropertyChanged(args.OldValue as DependencyObjectCollection, args.NewValue as DependencyObjectCollection);
}
private static void SelectedIndexPropertyChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs args)
{
DependencyObjectSelector<T> _this = (DependencyObjectSelector<T>)dobj;
_this.UpdateSelectedObject();
}
#endregion
#region Static Data
public static readonly DependencyProperty ReferencesProperty =
DependencyProperty.Register(nameof(References),
typeof(DependencyObjectCollection),
typeof(DependencyObjectSelector<T>),
new PropertyMetadata(null, ReferencesPropertyChanged));
public static readonly DependencyProperty SelectedIndexProperty =
DependencyProperty.Register(nameof(SelectedIndex),
typeof(Int32),
typeof(DependencyObjectSelector<T>),
new PropertyMetadata(-1, SelectedIndexPropertyChanged));
public static readonly DependencyProperty SelectedObjectProperty =
DependencyProperty.Register(nameof(SelectedObject),
typeof(T),
typeof(DependencyObjectSelector<T>),
new PropertyMetadata(default(T)));
#endregion
}
As you can see, this class holds a collection of references and binds its SelectedObject property to the Value of the appropriate reference.
This binding is updated when SelectedIndex changes, and when the reference collection itself changes.
These classes obviously can't be used in XAML as they are parameterized by type T (which must derive from DependencyObject). However, it's
a simple matter to subclass them:
public sealed class BrushReference : DependencyObjectReference<Brush>
{
}
public sealed class BrushSelector : DependencyObjectSelector<Brush>
{
}
The trick now is to place a BrushSelector in some accessible ResourceDictionary (such as your Page's Resources) and then bind
to its SelectedObject property:
<Page.Resources>
<mynamespace:BrushSelector x:Key="MyBrushSelector" SelectedIndex="{x:Bind Path=MyViewModel.MyBrushIndex, Mode=OneWay}">
<mynamespace:BrushReference Value="{ThemeResource SystemControlForegroundAccentColor}"/>
<mynamespace:BrushReference Value="{ThemeResource SystemControlForegroundBaseHighBrush}"/>
<mynamespace:BrushReference Value="Red"/>
<mynamespace:BrushReference Value="Wheat"/>
</mynamespace:BrushSelector>
</Page.Resources>
<!-- ... -->
<TextBlock
Text="..."
Foreground="{Binding Source={StaticResource MyBrushSelector}, Path=SelectedObject}"
/>
Note that it's not necessary to specify a <DependencyObjectCollection> when defining the BrushSelector in XAML because of
the [ContentProperty] attribute on the selector class.
A few other comments -- first, I'd prefer to have the SelectedObject be a read-only DependencyProperty, as it should never
be set by markup or code outside the selector, but UWP doesn't yet support that. Second, the References property must be
of type DependencyObjectCollection and the property itself must be a DependencyProperty or the theme changes don't propagate
correctly. Finally, you can even use your own theme resources, and if your app doesn't specify an explicit theme, when you change
the theme in Windows Control Panel (e.g., Light -> Dark or vice-versa), those colors will update as well.

Related

Xamarin.Forms (XAML): Different layouts depending on a condition

Is there a way to choose what layout initialize depending on one condition? I have a Grid for football stats but if myViewModel.Sport == Sports.Basketball I'd like to load a completely different layout.
I tried something like this with Datatrigger in each View but it seems a mess for me:
<Label Text="{Binding Goals}"
Grid.Row="1" Grid.Column="0">
<Label.Triggers>
<DataTrigger TargetType="Label"
Binding="{Binding Sport}"
Value="1">
<Setter Property="Text"
Value="{Binding Points}"/>
</DataTrigger>
</Label.Triggers>
</Label>
I show "goals" but if the Sports enum value is 1 (Sports.Basketball) I change to "points". I want to do this with lots of Labels and even Images so I need a proper way to do it.
Could someone help me? I need to load a different Grid depending on the Sport Property of my ViewModel.
Another thing you could do is place each separate sport into it's own view, add all the views to your page and set their IsVisible property depending on which sport you want to show.
An example would look like this in pseudo-code:
<Page>
<Grid>
<BasketballView IsVisible="{Binding IsBasketball}">
<SoccerView IsVisible="{Binding IsSoccer}">
<FootballView IsVisible="{Binding IsFootball}">
</Grid>
</Page>
Then set the appropriate boolean values from the ViewModel.
To use DataTemplateSelector to solve this, as mentioned by #StephaneDelcroix, you'll want a custom class that has ItemsSource and ItemTemplate properties.
I haven't thought through / tested how DataTemplateSelector would be used with this; anyone is welcome to add that to this answer.
using System.Collections;
using Xamarin.Forms;
namespace YourNamespace
{
// From https://forums.xamarin.com/discussion/19874/listview-inside-stacklayout-a-height-problem/p2, #maxx313.
public class TemplatedStack : StackLayout
{
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IList), typeof(TemplatedStack), propertyChanged: OnItemsSourceChanged);
public IList ItemsSource
{
get { return (IList)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
private static void OnItemsSourceChanged(BindableObject pObj, object pOldVal, object pNewVal)
{
var layout = pObj as TemplatedStack;
if (layout != null && layout.ItemTemplate != null)
{
layout.BuildLayout();
layout.ForceLayout();
}
}
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(TemplatedStack), propertyChanged: OnItemTemplateChanged);
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
private static void OnItemTemplateChanged(BindableObject pObj, object pOldVal, object pNewVal)
{
var layout = pObj as TemplatedStack;
if (layout != null && layout.ItemsSource != null)
layout.BuildLayout();
}
private void BuildLayout()
{
Children.Clear();
foreach (var item in ItemsSource)
{
var view = (View)ItemTemplate.CreateContent();
view.BindingContext = item;
Children.Add(view);
}
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
return base.OnMeasure(widthConstraint, heightConstraint);
}
}
}
In your XAML, do
<yourXmlns:TemplatedStack .../>
where yourXmlns must be an xmlns declaration at top of your XAML.
Usage of ItemsSource and ItemTemplate properties is similar to how you would bind an items collection and template to a ListView.
(The reason NOT to use a ListView here, is that ListView may interfere with touch events, and adds extra layout cost.)
Bind to this a collection containing a single item.
E.g. for this question, that item would be the specific sport being viewed.

Binding to a different ElementName on Windows 10 UWP using StateTriggers

I have a Flyout with a helper to help bind to a Parent Element which determine its PlacementTarget property.
The line that I am trying to adjust is
helpers:FlyoutHelper.Parent="{Binding ElementName=appBarDelete}"
It works fine on Desktop but on Windows Mobile, I want to bind to a different ElementName. Is there a way to create something like conditional binding depending on whether it run on Mobile or Desktop?
I've tried binding to a string property in my ViewModel but the helper complains as it expects a FrameworkElement. I thought ElementName can be any string and perhaps there's an internal converter that convert this string to its FrameworkElement?
Any ideas?
<AppBarButton x:Name="menuZoom" Label="Thumbnail Size" >
<FlyoutBase.AttachedFlyout>
<Flyout x:Name="flyOut" helpers:FlyoutHelper.IsOpen="{Binding IsOpen, Mode=TwoWay}" helpers:FlyoutHelper.Parent="{Binding ElementName=appBarDelete}">
<StackPanel Width="240">
<TextBlock Text="Desired Size"/>
<Slider Minimum="50" Maximum="500" Value="{Binding ImageDesiredWidth, Mode=TwoWay}" />
<Button Content="OK" Command="{Binding CloseCommand}" HorizontalAlignment="Right" />
</StackPanel>
</Flyout>
</FlyoutBase.AttachedFlyout>
Here's my FloutHelper class
public static class FlyoutHelper
{
public static readonly DependencyProperty IsVisibleProperty =
DependencyProperty.RegisterAttached(
"IsOpen", typeof(bool), typeof(FlyoutHelper),
new PropertyMetadata(true, IsOpenChangedCallback));
public static readonly DependencyProperty ParentProperty =
DependencyProperty.RegisterAttached(
"Parent", typeof(FrameworkElement), typeof(FlyoutHelper), null);
public static void SetIsOpen(DependencyObject element, bool value)
{
element.SetValue(IsVisibleProperty, value);
}
public static bool GetIsOpen(DependencyObject element)
{
return (bool)element.GetValue(IsVisibleProperty);
}
private static async void IsOpenChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var fb = d as FlyoutBase;
if (fb == null)
return;
if ((bool)e.NewValue)
{
try
{
fb.Closed += flyout_Closed;
fb.ShowAt(GetParent(d));
}
catch (Exception msg)
{
var dialog = new MessageDialog(msg.Message);
await dialog.ShowAsync();
}
}
else
{
fb.Closed -= flyout_Closed;
fb.Hide();
}
}
private static void flyout_Closed(object sender, object e)
{
// When the flyout is closed, sets its IsOpen attached property to false.
SetIsOpen(sender as DependencyObject, false);
}
public static void SetParent(DependencyObject element, FrameworkElement value)
{
element.SetValue(ParentProperty, value);
}
public static FrameworkElement GetParent(DependencyObject element)
{
return (FrameworkElement)element.GetValue(ParentProperty);
}
}
Update
I've managed to set the Parent property from the code behind using the following.
private void setFlyoutParent()
{
if (DeviceTypeHelper.GetDeviceFormFactorType() == DeviceFormFactorType.Phone)
{
FlyoutHelper.SetParent(this.flyOut, this.appBarPath);
}
else
{
FlyoutHelper.SetParent(this.flyOut, this.appBarDelete);
}
}
That works fine but I want to use theVisualState.StateTriggers and set the property on MinWindowWidth
AdaptiveTrigger
As you updated the question, it is also possible to use the AdaptiveTrigger to do what you need. Because the FlyoutHelper.ParentProperty is an attached property, you will need to use parentheses to set it like this:
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MaxWindowWidth="..." />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="flyOut.(helpers:FlyoutHelper.Parent)"
Value="{Binding ElementName=theElementOnMobile}" />
</VisualState.Setters>
</VisualState>
Alternative - in code
The easiest solution would be to do this in the code-behind of the page. In the constructor, after InitializeComponent call add the following:
if ( AnalyticsInfo.VersionInfo.DeviceFamily.Contains( "Mobile" ) )
{
flyOut.SetValue( FlyoutHelper.ParentProperty, theElementOnMobile );
}
else
{
flyOut.SetValue( FlyoutHelper.ParentProperty, appBarDelete );
}
To make this cleaner, you can use the DeviceUtils provided by Template 10 - see on GitHub. With this, you can simplify the code to:
flyOut.SetValue( FlyoutHelper.ParentProperty,
DeviceUtils.CurrentDeviceFamily == DeviceFamilies.Mobile ?
theElementOnMobile : appBarDelete );

Xaml two way binding of textbox to observable collection

In my WP8 app I have a class, which has a ObservableCollection<ObservableCollection<int>> property called Matrix.
I want to display these matrices using items control.
<ItemsControl ItemsSource="{Binding FirstMatrix.Matrix}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The code works as far as displaying is concerned (it's filled with zeros which is a default value). But I also want to allow changes in TextBoxes which would be reflected in Matrix property - now the TextBoxes can't be changed, because their value is bound one way to Matrix cells I guess. I tried setting <TextBox Text="{Binding Mode=TwoWay}" />or sth similar but it doesn't seem to work.
Any ideas how should the data be bound ?
EDIT:
I have implemented the INotifyPropertyChanged.
Here is a part of my class:
public partial class CalcMatrix : INotifyPropertyChanged
{
public ObservableCollection<ObservableCollection<int>> Matrix
{
get { return _matrix; }
set
{
_matrix = value;
OnPropertyChanged("Matrix");
}
}
private ObservableCollection<ObservableCollection<int>> _matrix;
private void OnPropertyChanged(string argName)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(argName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
I think the reason the TexBoxes don't change is because the binding is one-way - the Text is always what is inside the Matrix. I believe that i should somehow change the XAML binding to TwoWay or something but don't know how. Any ideas ?
Two way mode binding require path (why? see this SO answer), so you can't do it just like {Binding Mode=TwoWay}, it has to be something like {Binding SomePath, Mode=TwoWay}. Therefore, in this case you have to wrap matrix item to be some class instead of plain int and put that int as property's value of that class.
//your Matrix property type become:
...
public ObservableCollection<ObservableCollection<MatrixElement>> Matrix
...
//MatrixElement class is something like:
public class MatrixElement : INotifyPropertyChanged
{
private int _value;
public int Value
{
get { return _value; }
set {
_value = value;
OnPropertyChanged("Value");
}
}
....
}
//then you can bind TextBox in two way mode
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, Mode=TwoWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
...
The reason it didnt work is that the itemsource is a list of Matrix and you are not making any changes to the list iteself like adding or removing from a list instead you are changing a property of the item present in list I assume you are using an ObservableCollection....
So you need to implement a INotifyPropertyChanged interface to tell the UI that Hey I am changed please update yourself....
class YourClass : INotifyPropertyChanged
{
private string yourProperty;
public string YourPropety{
get{
return yourProperty;
}
set{
if (value != this.yourProperty)
{
this.yourProperty = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Windows 8.1: Behaviors on Flyouts don't Work

I am developing a windows 8.1 app using VS 2013 and MVVM Light.
The following code shows the behavior in a flyout within an appbar:
<AppBarButton.Flyout>
<Flyout x:Name="FlyoutCalculator"
Placement="Top"
FlyoutPresenterStyle="{StaticResource FlyoutPresenterBaseStyle}">
<uc:Calculator ApplyCommand="{Binding CancelCommand}"
CancelCommand="{Binding CancelCommand}"
Available="{Binding AvailableCounter, Mode=OneWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Opening">
<core:InvokeCommandAction Command="{Binding ShowCurrentCostsCommand}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Flyout>
</AppBarButton.Flyout>
Unfortunately I get an exception while compiling the app:
WinRT-Informationen: Cannot add instance of type Microsoft.Xaml.Interactions.Core.EventTriggerBehavior to a collection of type Microsoft.Xaml.Interactivity.BehaviorCollection
Other Behaviors in the View do work, does someone know a solution to this?
Extremely late answer here, but I had the same issue and came up with a solution after finding this post.
I just created a custom behavior specifically for flyouts, used like this. OpenActions will execute when the flyout is opened, and CloseActions will execute when the flyout closes. In this case, I wanted the bottom app bar to not be visible when the flyout was open.
<Flyout Placement="Full">
<i:Interaction.Behaviors>
<behaviors:FlyoutBehavior>
<behaviors:FlyoutBehavior.OpenActions>
<core:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" TargetObject="{Binding ElementName=CommandBar}" />
</behaviors:FlyoutBehavior.OpenActions>
<behaviors:FlyoutBehavior.CloseActions>
<core:ChangePropertyAction PropertyName="Visibility" Value="Visible" TargetObject="{Binding ElementName=CommandBar}" />
</behaviors:FlyoutBehavior.CloseActions>
</behaviors:FlyoutBehavior>
</i:Interaction.Behaviors>
<Grid>
...
</Grid>
</Flyout>
Code is here:
class FlyoutBehavior : DependencyObject, IBehavior
{
public DependencyObject AssociatedObject { get; private set; }
public void Attach(Windows.UI.Xaml.DependencyObject associatedObject)
{
var flyout = associatedObject as FlyoutBase;
if (flyout == null)
throw new ArgumentException("FlyoutBehavior can be attached only to FlyoutBase");
AssociatedObject = associatedObject;
flyout.Opened += FlyoutOpened;
flyout.Closed += FlyoutClosed;
}
public void Detach()
{
var flyout = AssociatedObject as FlyoutBase;
if (flyout != null)
{
flyout.Opened -= FlyoutOpened;
flyout.Closed -= FlyoutClosed;
}
}
public static readonly DependencyProperty OpenActionsProperty =
DependencyProperty.Register("OpenActions", typeof(ActionCollection), typeof(FlyoutBehavior), new PropertyMetadata(null));
public ActionCollection OpenActions
{
get { return GetValue(OpenActionsProperty) as ActionCollection; }
set { SetValue(OpenActionsProperty, value); }
}
public static readonly DependencyProperty CloseActionsProperty =
DependencyProperty.Register("CloseActions", typeof(ActionCollection), typeof(FlyoutBehavior), new PropertyMetadata(null));
public ActionCollection CloseActions
{
get { return GetValue(CloseActionsProperty) as ActionCollection; }
set { SetValue(CloseActionsProperty, value); }
}
private void FlyoutOpened(object sender, object e)
{
foreach (IAction action in OpenActions)
{
action.Execute(AssociatedObject, null);
}
}
private void FlyoutClosed(object sender, object e)
{
foreach (IAction action in CloseActions)
{
action.Execute(AssociatedObject, null);
}
}
public FlyoutBehavior()
{
OpenActions = new ActionCollection();
CloseActions = new ActionCollection();
}
}
I do not have a solution but:
I'm not using Flyouts in my Windows 8.1 App, I'm using a UserControl on which I have added a EventTriggerBehavior as you did. And I get exactly the same Errormessage from VisualStudio at runtime.
As I am using a RoutedEventHandler this could cause the Problem as you use
EventHandler<object> Opening
as the Trigger for the Behavior. But that is just an idea of what is the problem.
For me I have found an answer:
I have changed the Type of my RoutedEventHandler to be just a normal EventHandler. And the Method inside the CodeBehind which triggers the RoutedEventHandler is invoked with only the sender, because I dont know how to convert RoutedEventArgs into EventArgs, but as long as I dont need the EventArgs it's not a problem.
You could also make a workaround by creating a UserControl with a Flyout Control and make the Opening Event public to the Page where you use it. Then you can add the EventTriggerBehavior to the UserControl and connect it to your custom Opening Event and you should get the expected behavior.

PropertyChanged always null

This is my MainPage.xaml :-
<UserControl x:Class="SilverlightPlainWCF.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" xmlns:my="clr-namespace:SilverlightPlainWCF.CustomersServiceRef" Loaded="UserControl_Loaded">
<UserControl.Resources>
<CollectionViewSource x:Key="customerViewSource" d:DesignSource="{d:DesignInstance my:Customer, CreateList=True}" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<sdk:DataGrid AutoGenerateColumns="False" Height="426" HorizontalAlignment="Left" ItemsSource="{Binding}" Margin="12,12,0,0" Name="customerDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected" VerticalAlignment="Top" Width="776">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn x:Name="addressColumn" Binding="{Binding Path=Address}" Header="Address" Width="SizeToHeader" />
<sdk:DataGridTextColumn x:Name="cityColumn" Binding="{Binding Path=City}" Header="City" Width="SizeToHeader" />
<sdk:DataGridTextColumn x:Name="companyNameColumn" Binding="{Binding Path=CompanyName}" Header="Company Name" Width="SizeToHeader" />
<sdk:DataGridTextColumn x:Name="contactNameColumn" Binding="{Binding Path=ContactName}" Header="Contact Name" Width="SizeToHeader" />
<sdk:DataGridTextColumn x:Name="contactTitleColumn" Binding="{Binding Path=ContactTitle}" Header="Contact Title" Width="SizeToHeader" />
<sdk:DataGridTextColumn x:Name="countryColumn" Binding="{Binding Path=Country}" Header="Country" Width="SizeToHeader" />
<sdk:DataGridTextColumn x:Name="customerIDColumn" Binding="{Binding Path=CustomerID}" Header="Customer ID" Width="SizeToHeader" />
<sdk:DataGridTextColumn x:Name="faxColumn" Binding="{Binding Path=Fax}" Header="Fax" Width="SizeToHeader" />
<sdk:DataGridTextColumn x:Name="phoneColumn" Binding="{Binding Path=Phone}" Header="Phone" Width="SizeToHeader" />
<sdk:DataGridTextColumn x:Name="postalCodeColumn" Binding="{Binding Path=PostalCode}" Header="Postal Code" Width="SizeToHeader" />
<sdk:DataGridTextColumn x:Name="regionColumn" Binding="{Binding Path=Region}" Header="Region" Width="SizeToHeader" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>
</UserControl>
This is my MainPage.xaml.cs :-
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using SilverlightPlainWCF.CustomersServiceRef;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace SilverlightPlainWCF
{
public partial class MainPage : UserControl, INotifyPropertyChanged
{
public MainPage()
{
InitializeComponent();
this.DataContext = Customers;
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
public ObservableCollection<Customer> customers;
public ObservableCollection<Customer> Customers
{
get { return customers; }
set
{
customers = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Customers"));
}
}
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
CustomersServiceClient objCustomersServiceClient = new CustomersServiceClient();
objCustomersServiceClient.GetAllCustomersCompleted += (s, res) =>
{
if (res.Error == null)
{
Customers = new ObservableCollection<Customer>(res.Result);
}
else
{
MessageBox.Show(res.Error.Message);
}
};
objCustomersServiceClient.GetAllCustomersAsync();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// Do not load your data at design time.
// if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
// {
// //Load your data here and assign the result to the CollectionViewSource.
// System.Windows.Data.CollectionViewSource myCollectionViewSource = (System.Windows.Data.CollectionViewSource)this.Resources["Resource Key for CollectionViewSource"];
// myCollectionViewSource.Source = your data
// }
// Do not load your data at design time.
// if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
// {
// //Load your data here and assign the result to the CollectionViewSource.
// System.Windows.Data.CollectionViewSource myCollectionViewSource = (System.Windows.Data.CollectionViewSource)this.Resources["Resource Key for CollectionViewSource"];
// myCollectionViewSource.Source = your data
// }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
If i just move the line :-
this.DataContext = Customers;
from constructor to here :-
if (res.Error == null)
{
Customers = new ObservableCollection<Customer>(res.Result);
this.DataContext = Customers;
}
It works fine and I get all the data. What might be the problem?
The reason that it didn't work when you put it in the constructor is that there is not yet any value in the customers field at that moment.
You will only get the value when MainPage_Loaded is triggered, which will not happen because of the following line in your XAML:
Loaded="UserControl_Loaded"
That will execute UserControl_Loaded and not MainPage_Loaded. What you can do is call MainPage_Loaded from UserControl_Loaded, which probably is not what you intend to do. So in that case you should change your XAML instead to:
Loaded="MainPage_Loaded"
And you can delete UserControl_Loaded altogether since you are not using it anymore.
And as for the assigning of the result to the DataGrid, you can actually do it directly by assigning the result straight to the DataContext instead of going through the Customers property.
But if you insist to assign it to the Customers property and have the DataGrid updated accordingly, then the next easiest solution would be to include the following line somewhere in your Customers set method:
DataContext = value;
If you really, really insist that the DataGrid should update itself when the PropertyChanged event is triggered, without having you to code the DataContext = Customers row, then what you want is data binding. By binding the DataContext property to your Customers property, then the DataGrid will be updated when it receive the PropertyChanged event.
To declare the data binding in XAML, you would need to assign a name to your UserControl tag. Then you would assign the binding to the DataContext, something along this line:
DataContext="{Binding Path=Customers, ElementName=theUserControlName}"
And if I were you, instead of having to implement the INotifyPropertyChanged interface, I would instead use Dependency Properties instead. Converting your example to use Dependency Property, I would have:
public static DependencyProperty CustomersProperty =
DependencyProperty.Register("Customers", typeof(ObservableCollection<Customer>), typeof(MainPage), null);
public ObservableCollection<Customer> Customers
{
get { return (ObservableCollection<Customer>) GetValue(CustomersProperty); }
set { SetValue(CustomersProperty, value); }
}
Just that, the property change notification will be handled by the framework.
I believe the problem is that in the constructor you do not have this line:
Customers = new ObservableCollection<Customer>(res.Result);
before you attempt to set the DataContext to that value.