opening the appbar in metro style apps using bindi

2019-08-01 19:56发布

问题:

My main page has the appbar and it is shared across different pages. I wrote the following code to open the appbar on the click of a gridview item.

XAML

<AppBar Opened="AppBar_Opened" IsOpen="{Binding IsAppBarOpen}">

Back end

private void Clock_SelectionChanged(object sender, SelectionChangedEventArgs e)
{            
    App.ViewModel.SelectedClock = (Clock)ThemeGridView.SelectedItem;
    App.WorldViewModel.IsAppBarOpen = true;                  
}

 private void ThemeGridView_ItemClick(object sender, ItemClickEventArgs e)
    {
        App.ViewModel.SelectedClock = (Clock)ThemeGridView.SelectedItem;
        App.WorldViewModel.IsAppBarOpen = true;
    } 

WorldViewModel

private bool _IsAppBarOpen;

public bool IsAppBarOpen
{
   get { return _IsAppBarOpen; }
   set { base.SetProperty(ref _IsAppBarOpen, value); }
}

GridView XAML

<GridView
        Grid.Row="1"
        Grid.Column="1"


         x:Name="ThemeGridView"                    
                ItemsSource="{Binding Clocks}" 
                ItemTemplate="{StaticResource WorldClockTemplate}"
                SelectionChanged="Clock_SelectionChanged"
                SelectionMode="None"
                IsItemClickEnabled="True"
                ItemClick="ThemeGridView_ItemClick"
                >
        <GridView.ItemsPanel>
            <ItemsPanelTemplate>
               <WrapGrid />
            </ItemsPanelTemplate>
        </GridView.ItemsPanel>
    </GridView>

But the appbar is not popping up when i select the gridview item. There is no binding error so its really mysterious!

回答1:

There is not way to bind IsOpen property according the msdn:

Note Binding to the IsOpen property doesn't have the expected results because the PropertyChanged notification doesn't occur when the property is set.



回答2:

<AppBar Opened="AppBar_Opened" IsOpen="{Binding IsAppBarOpen, **Mode=TwoWay**}">


回答3:

This works for me. I use MVVM Light Toolkit.

public bool AppBarIsOpen
{
    get { return this._appBarIsOpen; }

    set
    {
        if (this._appBarIsOpen == value) { return; }

        this._appBarIsOpen = value;
        this.RaisePropertyChanged("AppBarIsOpen"); // without INotifyPropertyChanged it doesn't work
    }
}


<AppBar
    IsSticky="True"
    IsOpen="{Binding Path=AppBarIsOpen, Mode=TwoWay}">


回答4:

Roman Weisert's answer correctly states the likely reason for it not working, although you also must make the binding two-way as Zack Weiner suggested (I'm not sure the reason for the latter since the binding is not working in the target-to-source direction anyway). The current value of AppBar.IsOpen may not be reflected by IsAppBarOpen of your view-model. When that's the case, and you try updating the value, it's possible that no PropertyChanged event is raised since you may not actually be updating a value. Instead, you may be just setting the value from false to false or from true to true. Most SetProperty method implementations do not raise the PropertyChanged event unless there is an actual change, and I presume yours is the same.

To fix the problem, consider modifying your view-model as follows:

public bool IsAppBarOpen
{
    get { return _IsAppBarOpen; } //changes initiated from UI not reflected
    set //not updated from UI
    {
        _IsAppBarOpen = value;
        base.OnPropertyChanged();
    }
}
bool _IsAppBarOpen;

The notable difference from your view-model's code, is that SetProperty is not called here so PropertyChanged is raised even when the backing store equals the newly introduced value. In case your base class differs, note that mine has an OnPropertyChanged method with the signature

void OnPropertyChanged( [CallerMemberName] string propertyName = null )

that serves to raise the PropertyChanged event.

I can see from your use of the code-behind, though, that you are not really following MVVM. If MVVM is not a concern to you, then you could forgo the IsAppBarOpen property altogether and just directly set AppBar.IsOpen. As someone who religiously adheres to MVVM, however, I do not recommend that you further head in that (sinful) direction.



回答5:

I had the same issue and using Caliburn Micro for WinRT and with this code worked for me:

<AppBar IsOpen="{Binding AppBarsOpen}" Name="MainAppBar" Padding="10,0,10,0" AutomationProperties.Name="Bottom App Bar">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50*" />
            <ColumnDefinition Width="50*" />
        </Grid.ColumnDefinitions>
        <StackPanel x:Name="LeftPanel" Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Left">
            <Button Name="ShowFlyout"  Style="{StaticResource BookmarksAppBarButtonStyle}" />
        </StackPanel>
        <StackPanel x:Name="RightPanel" Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Right">
            <Button Style="{StaticResource SaveAppBarButtonStyle}" />
        </StackPanel>
    </Grid>
</AppBar>

And that's your property in ViewModel:

public bool AppBarsOpen
{
    get { return _appBarsOpen; }
    set
    {
        if (value.Equals(_appBarsOpen)) return;
        _appBarsOpen = value;
        NotifyOfPropertyChange(() => AppBarsOpen);
    }
}


回答6:

Had the same issue, solved it by adding the Closed event and updating the ViewModel from the code behind. Saw no other way since TwoWay binding was not working as Roman pointed out.

XAML

<AppBar x:Name="BottomAppBar1"
                AutomationProperties.Name="Bottom App Bar"
                Closed="BottomAppBar1_Closed"
                IsOpen="{Binding IsOpen, Mode=TwoWay}"
                IsSticky="True">

C# Code behind

private void BottomAppBar1_Closed(object sender, object e)
{
  MainViewModel vm = this.DataContext as MainViewModel;
  vm.IsOpen = false;
}

C# MainViewModel

public const string IsOpenPropertyName = "IsOpen";

private bool isOpen = false;

/// <summary>
/// Sets and gets the IsOpen property.
/// Changes to that property's value raise the PropertyChanged event. 
/// </summary>
public bool IsOpen
{
  get
  {
    return isOpen;
  }
  set
  {
    RaisePropertyChanging(IsOpenPropertyName);
    isOpen = value;
    RaisePropertyChanged(IsOpenPropertyName);
  }
}


回答7:

You should bind both IsOpen and IsSticky two way because otherwise you will get problems with for example having to tap two time to unselect an item (once to close the app bar and once for unselecting) and also it's the will help having your app bar behave more standarly (will prevent the app bar to pop down on tap when an item is selected).
To show the app bar you will need to do the following (the order is important):

this.IsAppBarSticky = true;
this.IsAppBarOpen = true;

and to hide it, do the following:

this.IsAppBarSticky = false;
this.IsAppBarOpen = false;


回答8:

Another way to make this work without having to use a codebehind handler for app bar closed event:

public class AppBarClosedCommand
{
    public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand),
        typeof(AppBarClosedCommand), new PropertyMetadata(null, CommandPropertyChanged));


    public static void SetCommand(DependencyObject attached, ICommand value)
    {
        attached.SetValue(CommandProperty, value);
    }


    public static ICommand GetCommand(DependencyObject attached)
    {
        return (ICommand)attached.GetValue(CommandProperty);
    }


    private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // Attach click handler
        (d as AppBar).Closed += AppBar_onClose;
    }


    private static void AppBar_onClose(object sender, object e)
    {
        // Get GridView
        var appBar = (sender as AppBar);


        // Get command
        ICommand command = GetCommand(appBar);


        // Execute command
        command.Execute(e);
    }
}

then in the XAML you can use it like :

common:AppBarClosedCommand.Command="{Binding AppBarClosedCommand}"

with the command function looking like:

public void OnAppBarClosed()
    {
        AppBarOpen = false;
    }