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 );
Related
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.
I'm trying to create a MenuFlyout with ToggleMenuFlyoutItems where one and only one toggle is checked at any given moment. The toggles corresponds to ToggleViewModels, binding the IsChecked property of the toggle to an IsSelected property of the ToggleViewModel. Because I want to uncheck the previously checked toggle whenever a new toggle is checked I relay the setting of the IsSelected property to the MainViewModel that holds the collection of ToggleViewModels.
Button with flyout defined in MainPage.xaml
<Button Content="Asdf">
<Button.Flyout>
<MenuFlyout>
<ToggleMenuFlyoutItem
Text="{x:Bind _viewModel.ToggleCollection[0].Name}"
IsChecked="{x:Bind _viewModel.ToggleCollection[0].IsSelected, Mode=TwoWay}" />
<ToggleMenuFlyoutItem
Text="{x:Bind _viewModel.ToggleCollection[1].Name}"
IsChecked="{x:Bind _viewModel.ToggleCollection[1].IsSelected, Mode=TwoWay}" />
<ToggleMenuFlyoutItem
Text="{x:Bind _viewModel.ToggleCollection[2].Name}"
IsChecked="{x:Bind _viewModel.ToggleCollection[2].IsSelected, Mode=TwoWay}" />
</MenuFlyout>
</Button.Flyout>
</Button>
MainPageViewModel:
public class MainViewModel : BindableBase
{
public MainViewModel()
{
ToggleCollection = new ObservableCollection<ToggleViewModel>();
var selectToggleAction = new Action<ToggleViewModel>(param => SetToggleSelection(param));
for (int i = 0; i < 3; i++)
{
ToggleCollection.Add(new ToggleViewModel($"Item {i}", selectToggleAction));
}
}
public ObservableCollection<ToggleViewModel> ToggleCollection { get; private set; }
private void SetToggleSelection(ToggleViewModel toggle)
{
var selectedToggle = ToggleCollection.SingleOrDefault(t => t.IsSelected);
if (selectedToggle != toggle)
{
selectedToggle?.SetSelection(false);
toggle.SetSelection(true);
}
}
}
ToggleViewModel:
public class ToggleViewModel : BindableBase
{
private Action<ToggleViewModel> _selectToggleAction;
private bool _isSelected;
public ToggleViewModel(string name, Action<ToggleViewModel> action)
{
Name = name;
_selectToggleAction = action;
}
public string Name { get; set; }
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_selectToggleAction(this);
base.OnPropertyChanged();
}
}
}
public void SetSelection(bool selected)
{
_isSelected = selected;
base.OnPropertyChanged("IsSelected");
}
}
Now all the code above works very well. The problem occurs when I try to use regular bindings instead of compiled ones:
<ToggleMenuFlyoutItem
Text="{Binding ToggleCollection[0].Name}"
IsChecked="{Binding ToggleCollection[0].IsSelected, Mode=TwoWay}" />
Binding the properties like this I'm suddenly able to uncheck the currently checked toggle so that none is selected. This is due to the getter of the IsSelected property not being called when I raise the OnPropertyChanged in the setter of the IsSelected property (the reason for using regular bindings is that I want to create the toggles dynamically in code behind, but to illustrate the problem XAML works just as well).
Can anyone explain to me why the {x:Bind} in this case works but not the {Binding}?
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.
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.
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));