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.
Related
i have run into a problem where i want to show a list of gradient stops in a listbox. The problem is that putting the gradientstops in a collection of type ObservableCollection works, but using a GradientStopCollection does not.
When i Use GradientStopCollection, the items that are in the list before the window is initialized are shown, but when a button is pressed to add a third item, the UI is not updated.
Calling OnPropertyChanged does not result in the UI being updated. I have made a small example to try to reproduce the problem.
So how can get the window to correctly update even when i use a gradientstop collection?
using System.Windows;
using System.Windows.Media;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new ViewModel();
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ViewModel vm = (DataContext as ViewModel);
vm.Collection.Add(new GradientStop(Colors.Red, 0.5));
//This line has no effect:
vm.OnPropertyChanged("Collection");
}
}
}
Viewmodel:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
namespace WpfApp1
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public GradientStopCollection Collection
{
get
{
return collection;
}
set
{
collection = value;
}
}
//Replacing GradientStopCollection
// with ObservableCollection<GradientStop> makes it work
GradientStopCollection collection;
public ViewModel()
{
GradientStop a = new GradientStop(Colors.Green, 0);
GradientStop b = new GradientStop(Colors.Yellow, 1.0);
collection = new GradientStopCollection() { a, b } ;
OnPropertyChanged("Collection");
}
public void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class Converter : IValueConverter
{
public object Convert(object value, Type targettype, object parameter, CultureInfo cultureInfo)
{
if (value is Color color)
return new SolidColorBrush(color);
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targettype, object parameter, CultureInfo cultureInfo)
{
throw new NotImplementedException();
}
}
}
And finally the xaml:
<Grid>
<Grid.Resources>
<local:Converter x:Key="ColorConverter"/>
<DataTemplate DataType="{x:Type GradientStop}">
<TextBlock
Width="50"
Background="{Binding Color, Converter={StaticResource ColorConverter}}"
Text="block"
/>
</DataTemplate>
</Grid.Resources>
<ListBox
x:Name="GradientListBox"
Width="72"
Height="92"
ItemsSource="{Binding Collection}" />
<Button Content="Button" HorizontalAlignment="Left" Margin="169,264,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
I don't think that there is any easy way around this problem.
You could create your own collection class, inheriting from GradientStopCollection and implementing the interface INotifyCollectionChanged, effectively making an ObservableGradientStopCollection.
You can probably find an implementation of INotifyCollectionChanged as an excmple.
It might be easier, just to keep two collections, although it seems like bad style.
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}?
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 );
I develop an application, and faced a problem with following part.
I have a 'textblock' that binds exact item in observable collection of items('Items') and shows the number. Each item has a property, called 'Count'.
When user taps current textblock, which is in stackpanel of longlistselector (binding Items), I need this textblock to show new number(idea is each time user taps textblock, number increases by 1).
The only method I discovered is each time Navigate to current pivot item(where textblock is situated) to force pivot item's reload data from observable collection, where updated items(and 'Count' property) are stored. But it is slow and doesn't work good.
Please, advice how to make the number appear in the textblock each time user taps on current textblock.
<phone:PivotItem Name="Documents" Header="Documents" Margin="0" >
<Grid>
<phone:LongListSelector ItemsSource="{Binding Items}" >
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Height="95" >
<TextBlock Tap="Plus_Tap" Visibility="Visible" Width="20" Height="50" Text="{Binding Count}" FontFamily="Segoe WP Light"
FontSize="35" Foreground="White" Margin="2,0,0,2"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
</phone:PivotItem>
namespace PackMan
{
public partial class CategoryList : PhoneApplicationPage
{
public CategoryList()
{
InitializeComponent();
DataContext = App.ViewModel;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
private void Plus_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
PackList selectedList = (sender as TextBlock).DataContext as PackList;
selectedList.Count += 1;
NavigationService.Navigate(new Uri("/CategoryList.xaml?Documents", UriKind.Relative));
}
}
}
How to refresh the data in pivot item after updating data through xaml UI?
here a guy in the very end of his posted answer to his own question wrote: "If you are using the observableCollection than no need to refresh the page. It is the magic of ViewModel." But I load data from 'Items' observable collection, which is not in ViewModel, but declared in Packlist.cs. Maybe this will help.
Here I do increase Count property of the item in ObservableCollection "Items" :
private void Plus_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
PackList selectedList = (sender as TextBlock).DataContext as PackList;
selectedList.Count += 1;
I tap on textblock, and text of the textblock shows no changes. I tap return - which brings me back to menu page, than again I open pivot item page, and see the textblock's text(number) increases by one. Change is seen only when I reload page(pivot item), since I have this in its constructor and OnNavigatedTo method:
public CategoryList()
{
InitializeComponent();
DataContext = App.ViewModel;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
LoadData() - loads OBservable collection ("Items"), which has items, and Count as one of its properties:
public void LoadData()
{
this.Items = LoadNewLists();
this.IsDataLoaded = true;
}
Maybe now you could tell me how to make text of the textblock change instantly as I tap on it(textblock). I will be thankful for any advice, since this moment has stopped me from developing my app second day now.
I figured it out. I should have read about INotifyPropertyChanged Interface and created 'Count' property using this interface's method:
public int Count
{
get
{
return _count;
}
set
{
if (value != _count)
{
_count = value;
NotifyPropertyChanged("Count");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Hope it will be helpful to anyone.
I get an error:
Cannot find a Resource with the Name/Key ResourceString [Line: 94 Position: 104]
in my xaml:
<data:DataGridTemplateColumn x:Name="PriorityColumn" CanUserSort="True" Header="{Binding columnHeader_PriorityColumn, Source={StaticResource ResourceString}}">
But I can't bind element from resource to texblock
After some investigation I figured out that it's because I can only user Resource.resx to controls listed at:
http://msdn.microsoft.com/en-us/library/dd882554(v=vs.95).aspx
So if I have right, is there other way to bind value do header of DataGridTemplateColumn?
If I understand correctly you need to bind value from resource file. If that is the question then you should wrap Resource with INotifyPropertyChanged. Try to do something like this:
public class ResourceWrapper : INotifyPropertyChanged
{
private static readonly Resource resourceHelper = new Resource();
public Resource ResourceHelper
{
get { return resourceHelper; }
set { NotifyChange("ResourceHelper"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyChange(String name)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
In xaml do something like:
...
<UserControl.Resources>
<helper:ResourceWrapper x:Name="ResourceString" />
</UserControl.Resources>
...
and finally
<data:DataGridTemplateColumn x:Name="PriorityColumn" CanUserSort="True" Header="{Binding ResourceHelper.NeededString, Source={StaticResource ResourceString}}">
Hope it'll help you.