This commit is contained in:
Beatriz Stollnitz 2019-08-05 21:09:36 -07:00
Родитель dee2ba6b85
Коммит 30b5b78b81
29 изменённых файлов: 3910 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,46 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterBindableBaseSample", "BetterBindableBaseSample\BetterBindableBaseSample.csproj", "{452E068B-F7A8-473F-A41A-2515A3044B59}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|ARM = Debug|ARM
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|ARM = Release|ARM
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{452E068B-F7A8-473F-A41A-2515A3044B59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{452E068B-F7A8-473F-A41A-2515A3044B59}.Debug|Any CPU.Build.0 = Debug|Any CPU
{452E068B-F7A8-473F-A41A-2515A3044B59}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{452E068B-F7A8-473F-A41A-2515A3044B59}.Debug|ARM.ActiveCfg = Debug|ARM
{452E068B-F7A8-473F-A41A-2515A3044B59}.Debug|ARM.Build.0 = Debug|ARM
{452E068B-F7A8-473F-A41A-2515A3044B59}.Debug|ARM.Deploy.0 = Debug|ARM
{452E068B-F7A8-473F-A41A-2515A3044B59}.Debug|x64.ActiveCfg = Debug|x64
{452E068B-F7A8-473F-A41A-2515A3044B59}.Debug|x64.Build.0 = Debug|x64
{452E068B-F7A8-473F-A41A-2515A3044B59}.Debug|x64.Deploy.0 = Debug|x64
{452E068B-F7A8-473F-A41A-2515A3044B59}.Debug|x86.ActiveCfg = Debug|x86
{452E068B-F7A8-473F-A41A-2515A3044B59}.Debug|x86.Build.0 = Debug|x86
{452E068B-F7A8-473F-A41A-2515A3044B59}.Debug|x86.Deploy.0 = Debug|x86
{452E068B-F7A8-473F-A41A-2515A3044B59}.Release|Any CPU.ActiveCfg = Release|Any CPU
{452E068B-F7A8-473F-A41A-2515A3044B59}.Release|Any CPU.Build.0 = Release|Any CPU
{452E068B-F7A8-473F-A41A-2515A3044B59}.Release|Any CPU.Deploy.0 = Release|Any CPU
{452E068B-F7A8-473F-A41A-2515A3044B59}.Release|ARM.ActiveCfg = Release|ARM
{452E068B-F7A8-473F-A41A-2515A3044B59}.Release|ARM.Build.0 = Release|ARM
{452E068B-F7A8-473F-A41A-2515A3044B59}.Release|ARM.Deploy.0 = Release|ARM
{452E068B-F7A8-473F-A41A-2515A3044B59}.Release|x64.ActiveCfg = Release|x64
{452E068B-F7A8-473F-A41A-2515A3044B59}.Release|x64.Build.0 = Release|x64
{452E068B-F7A8-473F-A41A-2515A3044B59}.Release|x64.Deploy.0 = Release|x64
{452E068B-F7A8-473F-A41A-2515A3044B59}.Release|x86.ActiveCfg = Release|x86
{452E068B-F7A8-473F-A41A-2515A3044B59}.Release|x86.Build.0 = Release|x86
{452E068B-F7A8-473F-A41A-2515A3044B59}.Release|x86.Deploy.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,20 @@
<Application
x:Class="BetterBindableBaseSample.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BetterBindableBaseSample">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--
Styles that define common aspects of the platform look and feel
Required by Visual Studio project and item templates
-->
<ResourceDictionary Source="Common/StandardStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

Просмотреть файл

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Application template is documented at http://go.microsoft.com/fwlink/?LinkId=234227
namespace BetterBindableBaseSample
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : Application
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used when the application is launched to open a specific file, to display
/// search results, and so forth.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
if (!rootFrame.Navigate(typeof(MainPage), args.Arguments))
{
throw new Exception("Failed to create initial page");
}
}
// Ensure the current window is active
Window.Current.Activate();
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: Save application state and stop any background activity
deferral.Complete();
}
}
}

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 801 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 329 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.1 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 429 B

Просмотреть файл

@ -0,0 +1,77 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Core;
using Windows.Foundation;
using Windows.UI.Core;
using Windows.UI.Xaml.Data;
namespace BetterBindableBaseSample
{
/// <summary>
/// Implementation of <see cref="INotifyPropertyChanged"/> to simplify models.
/// </summary>
[Windows.Foundation.Metadata.WebHostHidden]
public abstract class BetterBindableBase : INotifyPropertyChanged
{
private readonly CoreDispatcher _dispatcher;
protected BetterBindableBase()
{
if (!DesignMode.DesignModeEnabled)
{
_dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
}
}
/// <summary>
/// Multicast event for property change notifications.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Checks if a property already matches a desired value. Sets the property and
/// notifies listeners only when necessary.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to a property with both getter and setter.</param>
/// <param name="value">Desired value for the property.</param>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers that
/// support CallerMemberName.</param>
/// <returns>True if the value was changed, false if the existing value matched the
/// desired value.</returns>
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
/// <summary>
/// Uses the UI thread to notify listeners that a property value has changed.
/// </summary>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers
/// that support <see cref="CallerMemberNameAttribute"/>.</param>
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (_dispatcher == null || _dispatcher.HasThreadAccess)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
else
{
IAsyncAction doNotAwait = _dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => OnPropertyChanged(propertyName));
}
}
}
}

Просмотреть файл

@ -0,0 +1,162 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{452E068B-F7A8-473F-A41A-2515A3044B59}</ProjectGuid>
<OutputType>AppContainerExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BetterBindableBaseSample</RootNamespace>
<AssemblyName>BetterBindableBaseSample</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{BC8A1FFA-BEE3-4634-8014-F334798102B3};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<PackageCertificateKeyFile>BetterBindableBaseSample_TemporaryKey.pfx</PackageCertificateKeyFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
<OutputPath>bin\ARM\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .Net Framework and Windows SDK are automatically included -->
</ItemGroup>
<ItemGroup>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="BetterBindableBase.cs" />
<Compile Include="BooleanConverter.cs" />
<Compile Include="Common\BindableBase.cs" />
<Compile Include="Common\BooleanNegationConverter.cs" />
<Compile Include="Common\BooleanToVisibilityConverter.cs" />
<Compile Include="Common\LayoutAwarePage.cs" />
<Compile Include="Common\RichTextColumns.cs" />
<Compile Include="Common\SuspensionManager.cs" />
<Compile Include="DelegateCommand.cs" />
<Compile Include="MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ViewModel.cs" />
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
<None Include="BetterBindableBaseSample_TemporaryKey.pfx" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\Logo.png" />
<Content Include="Assets\SmallLogo.png" />
<Content Include="Assets\SplashScreen.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Common\ReadMe.txt" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="Common\StandardStyles.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="MainPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '11.0' ">
<VisualStudioVersion>11.0</VisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<UseSimulator>False</UseSimulator>
</PropertyGroup>
</Project>

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,45 @@
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace BetterBindableBaseSample
{
public abstract class BooleanConverter<T> : IValueConverter
{
protected BooleanConverter(T trueValue, T falseValue)
{
TrueValue = trueValue;
FalseValue = falseValue;
}
public T TrueValue { get; set; }
public T FalseValue { get; set; }
public object Convert(object value, Type targetType, object parameter, string language)
{
return Object.Equals(value, true) ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return Object.Equals(value, TrueValue);
}
}
public sealed class BooleanToVisibilityConverter : BooleanConverter<Visibility>
{
public BooleanToVisibilityConverter()
: base(Visibility.Visible, Visibility.Collapsed)
{
}
}
public sealed class NegatedBooleanToVisibilityConverter : BooleanConverter<Visibility>
{
public NegatedBooleanToVisibilityConverter()
: base(Visibility.Collapsed, Visibility.Visible)
{
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Windows.UI.Xaml.Data;
namespace BetterBindableBaseSample.Common
{
/// <summary>
/// Implementation of <see cref="INotifyPropertyChanged"/> to simplify models.
/// </summary>
[Windows.Foundation.Metadata.WebHostHidden]
public abstract class BindableBase : INotifyPropertyChanged
{
/// <summary>
/// Multicast event for property change notifications.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Checks if a property already matches a desired value. Sets the property and
/// notifies listeners only when necessary.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to a property with both getter and setter.</param>
/// <param name="value">Desired value for the property.</param>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers that
/// support CallerMemberName.</param>
/// <returns>True if the value was changed, false if the existing value matched the
/// desired value.</returns>
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
/// <summary>
/// Notifies listeners that a property value has changed.
/// </summary>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers
/// that support <see cref="CallerMemberNameAttribute"/>.</param>
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
using System;
using Windows.UI.Xaml.Data;
namespace BetterBindableBaseSample.Common
{
/// <summary>
/// Value converter that translates true to false and vice versa.
/// </summary>
public sealed class BooleanNegationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return !(value is bool && (bool)value);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return !(value is bool && (bool)value);
}
}
}

Просмотреть файл

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Graphics.Display;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
namespace BetterBindableBaseSample.Common
{
/// <summary>
/// Value converter that translates true to <see cref="Visibility.Visible"/> and false to
/// <see cref="Visibility.Collapsed"/>.
/// </summary>
public sealed class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return (value is bool && (bool)value) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value is Visibility && (Visibility)value == Visibility.Visible;
}
}
}

Просмотреть файл

@ -0,0 +1,543 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace BetterBindableBaseSample.Common
{
/// <summary>
/// Typical implementation of Page that provides several important conveniences:
/// <list type="bullet">
/// <item>
/// <description>Application view state to visual state mapping</description>
/// </item>
/// <item>
/// <description>GoBack, GoForward, and GoHome event handlers</description>
/// </item>
/// <item>
/// <description>Mouse and keyboard shortcuts for navigation</description>
/// </item>
/// <item>
/// <description>State management for navigation and process lifetime management</description>
/// </item>
/// <item>
/// <description>A default view model</description>
/// </item>
/// </list>
/// </summary>
[Windows.Foundation.Metadata.WebHostHidden]
public class LayoutAwarePage : Page
{
/// <summary>
/// Identifies the <see cref="DefaultViewModel"/> dependency property.
/// </summary>
public static readonly DependencyProperty DefaultViewModelProperty =
DependencyProperty.Register("DefaultViewModel", typeof(IObservableMap<String, Object>),
typeof(LayoutAwarePage), null);
private List<Control> _layoutAwareControls;
/// <summary>
/// Initializes a new instance of the <see cref="LayoutAwarePage"/> class.
/// </summary>
public LayoutAwarePage()
{
if (Windows.ApplicationModel.DesignMode.DesignModeEnabled) return;
// Create an empty default view model
this.DefaultViewModel = new ObservableDictionary<String, Object>();
// When this page is part of the visual tree make two changes:
// 1) Map application view state to visual state for the page
// 2) Handle keyboard and mouse navigation requests
this.Loaded += (sender, e) =>
{
this.StartLayoutUpdates(sender, e);
// Keyboard and mouse navigation only apply when occupying the entire window
if (this.ActualHeight == Window.Current.Bounds.Height &&
this.ActualWidth == Window.Current.Bounds.Width)
{
// Listen to the window directly so focus isn't required
Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated +=
CoreDispatcher_AcceleratorKeyActivated;
Window.Current.CoreWindow.PointerPressed +=
this.CoreWindow_PointerPressed;
}
};
// Undo the same changes when the page is no longer visible
this.Unloaded += (sender, e) =>
{
this.StopLayoutUpdates(sender, e);
Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated -=
CoreDispatcher_AcceleratorKeyActivated;
Window.Current.CoreWindow.PointerPressed -=
this.CoreWindow_PointerPressed;
};
}
/// <summary>
/// An implementation of <see cref="IObservableMap&lt;String, Object&gt;"/> designed to be
/// used as a trivial view model.
/// </summary>
protected IObservableMap<String, Object> DefaultViewModel
{
get
{
return this.GetValue(DefaultViewModelProperty) as IObservableMap<String, Object>;
}
set
{
this.SetValue(DefaultViewModelProperty, value);
}
}
#region Navigation support
/// <summary>
/// Invoked as an event handler to navigate backward in the page's associated
/// <see cref="Frame"/> until it reaches the top of the navigation stack.
/// </summary>
/// <param name="sender">Instance that triggered the event.</param>
/// <param name="e">Event data describing the conditions that led to the event.</param>
protected virtual void GoHome(object sender, RoutedEventArgs e)
{
// Use the navigation frame to return to the topmost page
if (this.Frame != null)
{
while (this.Frame.CanGoBack) this.Frame.GoBack();
}
}
/// <summary>
/// Invoked as an event handler to navigate backward in the navigation stack
/// associated with this page's <see cref="Frame"/>.
/// </summary>
/// <param name="sender">Instance that triggered the event.</param>
/// <param name="e">Event data describing the conditions that led to the
/// event.</param>
protected virtual void GoBack(object sender, RoutedEventArgs e)
{
// Use the navigation frame to return to the previous page
if (this.Frame != null && this.Frame.CanGoBack) this.Frame.GoBack();
}
/// <summary>
/// Invoked as an event handler to navigate forward in the navigation stack
/// associated with this page's <see cref="Frame"/>.
/// </summary>
/// <param name="sender">Instance that triggered the event.</param>
/// <param name="e">Event data describing the conditions that led to the
/// event.</param>
protected virtual void GoForward(object sender, RoutedEventArgs e)
{
// Use the navigation frame to move to the next page
if (this.Frame != null && this.Frame.CanGoForward) this.Frame.GoForward();
}
/// <summary>
/// Invoked on every keystroke, including system keys such as Alt key combinations, when
/// this page is active and occupies the entire window. Used to detect keyboard navigation
/// between pages even when the page itself doesn't have focus.
/// </summary>
/// <param name="sender">Instance that triggered the event.</param>
/// <param name="args">Event data describing the conditions that led to the event.</param>
private void CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher sender,
AcceleratorKeyEventArgs args)
{
var virtualKey = args.VirtualKey;
// Only investigate further when Left, Right, or the dedicated Previous or Next keys
// are pressed
if ((args.EventType == CoreAcceleratorKeyEventType.SystemKeyDown ||
args.EventType == CoreAcceleratorKeyEventType.KeyDown) &&
(virtualKey == VirtualKey.Left || virtualKey == VirtualKey.Right ||
(int)virtualKey == 166 || (int)virtualKey == 167))
{
var coreWindow = Window.Current.CoreWindow;
var downState = CoreVirtualKeyStates.Down;
bool menuKey = (coreWindow.GetKeyState(VirtualKey.Menu) & downState) == downState;
bool controlKey = (coreWindow.GetKeyState(VirtualKey.Control) & downState) == downState;
bool shiftKey = (coreWindow.GetKeyState(VirtualKey.Shift) & downState) == downState;
bool noModifiers = !menuKey && !controlKey && !shiftKey;
bool onlyAlt = menuKey && !controlKey && !shiftKey;
if (((int)virtualKey == 166 && noModifiers) ||
(virtualKey == VirtualKey.Left && onlyAlt))
{
// When the previous key or Alt+Left are pressed navigate back
args.Handled = true;
this.GoBack(this, new RoutedEventArgs());
}
else if (((int)virtualKey == 167 && noModifiers) ||
(virtualKey == VirtualKey.Right && onlyAlt))
{
// When the next key or Alt+Right are pressed navigate forward
args.Handled = true;
this.GoForward(this, new RoutedEventArgs());
}
}
}
/// <summary>
/// Invoked on every mouse click, touch screen tap, or equivalent interaction when this
/// page is active and occupies the entire window. Used to detect browser-style next and
/// previous mouse button clicks to navigate between pages.
/// </summary>
/// <param name="sender">Instance that triggered the event.</param>
/// <param name="args">Event data describing the conditions that led to the event.</param>
private void CoreWindow_PointerPressed(CoreWindow sender,
PointerEventArgs args)
{
var properties = args.CurrentPoint.Properties;
// Ignore button chords with the left, right, and middle buttons
if (properties.IsLeftButtonPressed || properties.IsRightButtonPressed ||
properties.IsMiddleButtonPressed) return;
// If back or foward are pressed (but not both) navigate appropriately
bool backPressed = properties.IsXButton1Pressed;
bool forwardPressed = properties.IsXButton2Pressed;
if (backPressed ^ forwardPressed)
{
args.Handled = true;
if (backPressed) this.GoBack(this, new RoutedEventArgs());
if (forwardPressed) this.GoForward(this, new RoutedEventArgs());
}
}
#endregion
#region Visual state switching
/// <summary>
/// Invoked as an event handler, typically on the <see cref="FrameworkElement.Loaded"/>
/// event of a <see cref="Control"/> within the page, to indicate that the sender should
/// start receiving visual state management changes that correspond to application view
/// state changes.
/// </summary>
/// <param name="sender">Instance of <see cref="Control"/> that supports visual state
/// management corresponding to view states.</param>
/// <param name="e">Event data that describes how the request was made.</param>
/// <remarks>The current view state will immediately be used to set the corresponding
/// visual state when layout updates are requested. A corresponding
/// <see cref="FrameworkElement.Unloaded"/> event handler connected to
/// <see cref="StopLayoutUpdates"/> is strongly encouraged. Instances of
/// <see cref="LayoutAwarePage"/> automatically invoke these handlers in their Loaded and
/// Unloaded events.</remarks>
/// <seealso cref="DetermineVisualState"/>
/// <seealso cref="InvalidateVisualState"/>
public void StartLayoutUpdates(object sender, RoutedEventArgs e)
{
var control = sender as Control;
if (control == null) return;
if (this._layoutAwareControls == null)
{
// Start listening to view state changes when there are controls interested in updates
Window.Current.SizeChanged += this.WindowSizeChanged;
this._layoutAwareControls = new List<Control>();
}
this._layoutAwareControls.Add(control);
// Set the initial visual state of the control
VisualStateManager.GoToState(control, DetermineVisualState(ApplicationView.Value), false);
}
private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs e)
{
this.InvalidateVisualState();
}
/// <summary>
/// Invoked as an event handler, typically on the <see cref="FrameworkElement.Unloaded"/>
/// event of a <see cref="Control"/>, to indicate that the sender should start receiving
/// visual state management changes that correspond to application view state changes.
/// </summary>
/// <param name="sender">Instance of <see cref="Control"/> that supports visual state
/// management corresponding to view states.</param>
/// <param name="e">Event data that describes how the request was made.</param>
/// <remarks>The current view state will immediately be used to set the corresponding
/// visual state when layout updates are requested.</remarks>
/// <seealso cref="StartLayoutUpdates"/>
public void StopLayoutUpdates(object sender, RoutedEventArgs e)
{
var control = sender as Control;
if (control == null || this._layoutAwareControls == null) return;
this._layoutAwareControls.Remove(control);
if (this._layoutAwareControls.Count == 0)
{
// Stop listening to view state changes when no controls are interested in updates
this._layoutAwareControls = null;
Window.Current.SizeChanged -= this.WindowSizeChanged;
}
}
/// <summary>
/// Translates <see cref="ApplicationViewState"/> values into strings for visual state
/// management within the page. The default implementation uses the names of enum values.
/// Subclasses may override this method to control the mapping scheme used.
/// </summary>
/// <param name="viewState">View state for which a visual state is desired.</param>
/// <returns>Visual state name used to drive the
/// <see cref="VisualStateManager"/></returns>
/// <seealso cref="InvalidateVisualState"/>
protected virtual string DetermineVisualState(ApplicationViewState viewState)
{
return viewState.ToString();
}
/// <summary>
/// Updates all controls that are listening for visual state changes with the correct
/// visual state.
/// </summary>
/// <remarks>
/// Typically used in conjunction with overriding <see cref="DetermineVisualState"/> to
/// signal that a different value may be returned even though the view state has not
/// changed.
/// </remarks>
public void InvalidateVisualState()
{
if (this._layoutAwareControls != null)
{
string visualState = DetermineVisualState(ApplicationView.Value);
foreach (var layoutAwareControl in this._layoutAwareControls)
{
VisualStateManager.GoToState(layoutAwareControl, visualState, false);
}
}
}
#endregion
#region Process lifetime management
private String _pageKey;
/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached. The Parameter
/// property provides the group to be displayed.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Returning to a cached page through navigation shouldn't trigger state loading
if (this._pageKey != null) return;
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
this._pageKey = "Page-" + this.Frame.BackStackDepth;
if (e.NavigationMode == NavigationMode.New)
{
// Clear existing state for forward navigation when adding a new page to the
// navigation stack
var nextPageKey = this._pageKey;
int nextPageIndex = this.Frame.BackStackDepth;
while (frameState.Remove(nextPageKey))
{
nextPageIndex++;
nextPageKey = "Page-" + nextPageIndex;
}
// Pass the navigation parameter to the new page
this.LoadState(e.Parameter, null);
}
else
{
// Pass the navigation parameter and preserved page state to the page, using
// the same strategy for loading suspended state and recreating pages discarded
// from cache
this.LoadState(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey]);
}
}
/// <summary>
/// Invoked when this page will no longer be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached. The Parameter
/// property provides the group to be displayed.</param>
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
var pageState = new Dictionary<String, Object>();
this.SaveState(pageState);
frameState[_pageKey] = pageState;
}
/// <summary>
/// Populates the page with content passed during navigation. Any saved state is also
/// provided when recreating a page from a prior session.
/// </summary>
/// <param name="navigationParameter">The parameter value passed to
/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
/// </param>
/// <param name="pageState">A dictionary of state preserved by this page during an earlier
/// session. This will be null the first time a page is visited.</param>
protected virtual void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
}
/// <summary>
/// Preserves state associated with this page in case the application is suspended or the
/// page is discarded from the navigation cache. Values must conform to the serialization
/// requirements of <see cref="SuspensionManager.SessionState"/>.
/// </summary>
/// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
protected virtual void SaveState(Dictionary<String, Object> pageState)
{
}
#endregion
/// <summary>
/// Implementation of IObservableMap that supports reentrancy for use as a default view
/// model.
/// </summary>
private class ObservableDictionary<K, V> : IObservableMap<K, V>
{
private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs<K>
{
public ObservableDictionaryChangedEventArgs(CollectionChange change, K key)
{
this.CollectionChange = change;
this.Key = key;
}
public CollectionChange CollectionChange { get; private set; }
public K Key { get; private set; }
}
private Dictionary<K, V> _dictionary = new Dictionary<K, V>();
public event MapChangedEventHandler<K, V> MapChanged;
private void InvokeMapChanged(CollectionChange change, K key)
{
var eventHandler = MapChanged;
if (eventHandler != null)
{
eventHandler(this, new ObservableDictionaryChangedEventArgs(change, key));
}
}
public void Add(K key, V value)
{
this._dictionary.Add(key, value);
this.InvokeMapChanged(CollectionChange.ItemInserted, key);
}
public void Add(KeyValuePair<K, V> item)
{
this.Add(item.Key, item.Value);
}
public bool Remove(K key)
{
if (this._dictionary.Remove(key))
{
this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
return true;
}
return false;
}
public bool Remove(KeyValuePair<K, V> item)
{
V currentValue;
if (this._dictionary.TryGetValue(item.Key, out currentValue) &&
Object.Equals(item.Value, currentValue) && this._dictionary.Remove(item.Key))
{
this.InvokeMapChanged(CollectionChange.ItemRemoved, item.Key);
return true;
}
return false;
}
public V this[K key]
{
get
{
return this._dictionary[key];
}
set
{
this._dictionary[key] = value;
this.InvokeMapChanged(CollectionChange.ItemChanged, key);
}
}
public void Clear()
{
var priorKeys = this._dictionary.Keys.ToArray();
this._dictionary.Clear();
foreach (var key in priorKeys)
{
this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
}
}
public ICollection<K> Keys
{
get { return this._dictionary.Keys; }
}
public bool ContainsKey(K key)
{
return this._dictionary.ContainsKey(key);
}
public bool TryGetValue(K key, out V value)
{
return this._dictionary.TryGetValue(key, out value);
}
public ICollection<V> Values
{
get { return this._dictionary.Values; }
}
public bool Contains(KeyValuePair<K, V> item)
{
return this._dictionary.Contains(item);
}
public int Count
{
get { return this._dictionary.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
{
return this._dictionary.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this._dictionary.GetEnumerator();
}
public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
{
int arraySize = array.Length;
foreach (var pair in this._dictionary)
{
if (arrayIndex >= arraySize) break;
array[arrayIndex++] = pair;
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,12 @@
The Common directory contains classes and XAML styles that simplify application development.
These are not merely convenient, but are required by most Visual Studio project and item templates.
If you need a variation on one of the styles in StandardStyles it is recommended that you make a
copy in your own resource dictionary. When right-clicking on a styled control in the design
surface the context menu includes an option to Edit a Copy to simplify this process.
Classes in the Common directory form part of your project and may be further enhanced to meet your
needs. Care should be taken when altering existing methods and properties as incompatible changes
will require corresponding changes to code included in a variety of Visual Studio templates. For
example, additional pages added to your project are written assuming that the original methods and
properties in Common classes are still present and that the names of the types have not changed.

Просмотреть файл

@ -0,0 +1,210 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Documents;
namespace BetterBindableBaseSample.Common
{
/// <summary>
/// Wrapper for <see cref="RichTextBlock"/> that creates as many additional overflow
/// columns as needed to fit the available content.
/// </summary>
/// <example>
/// The following creates a collection of 400-pixel wide columns spaced 50 pixels apart
/// to contain arbitrary data-bound content:
/// <code>
/// <RichTextColumns>
/// <RichTextColumns.ColumnTemplate>
/// <DataTemplate>
/// <RichTextBlockOverflow Width="400" Margin="50,0,0,0"/>
/// </DataTemplate>
/// </RichTextColumns.ColumnTemplate>
///
/// <RichTextBlock Width="400">
/// <Paragraph>
/// <Run Text="{Binding Content}"/>
/// </Paragraph>
/// </RichTextBlock>
/// </RichTextColumns>
/// </code>
/// </example>
/// <remarks>Typically used in a horizontally scrolling region where an unbounded amount of
/// space allows for all needed columns to be created. When used in a vertically scrolling
/// space there will never be any additional columns.</remarks>
[Windows.UI.Xaml.Markup.ContentProperty(Name = "RichTextContent")]
public sealed class RichTextColumns : Panel
{
/// <summary>
/// Identifies the <see cref="RichTextContent"/> dependency property.
/// </summary>
public static readonly DependencyProperty RichTextContentProperty =
DependencyProperty.Register("RichTextContent", typeof(RichTextBlock),
typeof(RichTextColumns), new PropertyMetadata(null, ResetOverflowLayout));
/// <summary>
/// Identifies the <see cref="ColumnTemplate"/> dependency property.
/// </summary>
public static readonly DependencyProperty ColumnTemplateProperty =
DependencyProperty.Register("ColumnTemplate", typeof(DataTemplate),
typeof(RichTextColumns), new PropertyMetadata(null, ResetOverflowLayout));
/// <summary>
/// Initializes a new instance of the <see cref="RichTextColumns"/> class.
/// </summary>
public RichTextColumns()
{
this.HorizontalAlignment = HorizontalAlignment.Left;
}
/// <summary>
/// Gets or sets the initial rich text content to be used as the first column.
/// </summary>
public RichTextBlock RichTextContent
{
get { return (RichTextBlock)GetValue(RichTextContentProperty); }
set { SetValue(RichTextContentProperty, value); }
}
/// <summary>
/// Gets or sets the template used to create additional
/// <see cref="RichTextBlockOverflow"/> instances.
/// </summary>
public DataTemplate ColumnTemplate
{
get { return (DataTemplate)GetValue(ColumnTemplateProperty); }
set { SetValue(ColumnTemplateProperty, value); }
}
/// <summary>
/// Invoked when the content or overflow template is changed to recreate the column layout.
/// </summary>
/// <param name="d">Instance of <see cref="RichTextColumns"/> where the change
/// occurred.</param>
/// <param name="e">Event data describing the specific change.</param>
private static void ResetOverflowLayout(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// When dramatic changes occur, rebuild the column layout from scratch
var target = d as RichTextColumns;
if (target != null)
{
target._overflowColumns = null;
target.Children.Clear();
target.InvalidateMeasure();
}
}
/// <summary>
/// Lists overflow columns already created. Must maintain a 1:1 relationship with
/// instances in the <see cref="Panel.Children"/> collection following the initial
/// RichTextBlock child.
/// </summary>
private List<RichTextBlockOverflow> _overflowColumns = null;
/// <summary>
/// Determines whether additional overflow columns are needed and if existing columns can
/// be removed.
/// </summary>
/// <param name="availableSize">The size of the space available, used to constrain the
/// number of additional columns that can be created.</param>
/// <returns>The resulting size of the original content plus any extra columns.</returns>
protected override Size MeasureOverride(Size availableSize)
{
if (this.RichTextContent == null) return new Size(0, 0);
// Make sure the RichTextBlock is a child, using the lack of
// a list of additional columns as a sign that this hasn't been
// done yet
if (this._overflowColumns == null)
{
Children.Add(this.RichTextContent);
this._overflowColumns = new List<RichTextBlockOverflow>();
}
// Start by measuring the original RichTextBlock content
this.RichTextContent.Measure(availableSize);
var maxWidth = this.RichTextContent.DesiredSize.Width;
var maxHeight = this.RichTextContent.DesiredSize.Height;
var hasOverflow = this.RichTextContent.HasOverflowContent;
// Make sure there are enough overflow columns
int overflowIndex = 0;
while (hasOverflow && maxWidth < availableSize.Width && this.ColumnTemplate != null)
{
// Use existing overflow columns until we run out, then create
// more from the supplied template
RichTextBlockOverflow overflow;
if (this._overflowColumns.Count > overflowIndex)
{
overflow = this._overflowColumns[overflowIndex];
}
else
{
overflow = (RichTextBlockOverflow)this.ColumnTemplate.LoadContent();
this._overflowColumns.Add(overflow);
this.Children.Add(overflow);
if (overflowIndex == 0)
{
this.RichTextContent.OverflowContentTarget = overflow;
}
else
{
this._overflowColumns[overflowIndex - 1].OverflowContentTarget = overflow;
}
}
// Measure the new column and prepare to repeat as necessary
overflow.Measure(new Size(availableSize.Width - maxWidth, availableSize.Height));
maxWidth += overflow.DesiredSize.Width;
maxHeight = Math.Max(maxHeight, overflow.DesiredSize.Height);
hasOverflow = overflow.HasOverflowContent;
overflowIndex++;
}
// Disconnect extra columns from the overflow chain, remove them from our private list
// of columns, and remove them as children
if (this._overflowColumns.Count > overflowIndex)
{
if (overflowIndex == 0)
{
this.RichTextContent.OverflowContentTarget = null;
}
else
{
this._overflowColumns[overflowIndex - 1].OverflowContentTarget = null;
}
while (this._overflowColumns.Count > overflowIndex)
{
this._overflowColumns.RemoveAt(overflowIndex);
this.Children.RemoveAt(overflowIndex + 1);
}
}
// Report final determined size
return new Size(maxWidth, maxHeight);
}
/// <summary>
/// Arranges the original content and all extra columns.
/// </summary>
/// <param name="finalSize">Defines the size of the area the children must be arranged
/// within.</param>
/// <returns>The size of the area the children actually required.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
double maxWidth = 0;
double maxHeight = 0;
foreach (var child in Children)
{
child.Arrange(new Rect(maxWidth, 0, child.DesiredSize.Width, finalSize.Height));
maxWidth += child.DesiredSize.Width;
maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
}
return new Size(maxWidth, maxHeight);
}
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,258 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace BetterBindableBaseSample.Common
{
/// <summary>
/// SuspensionManager captures global session state to simplify process lifetime management
/// for an application. Note that session state will be automatically cleared under a variety
/// of conditions and should only be used to store information that would be convenient to
/// carry across sessions, but that should be discarded when an application crashes or is
/// upgraded.
/// </summary>
internal sealed class SuspensionManager
{
private static Dictionary<string, object> _sessionState = new Dictionary<string, object>();
private static List<Type> _knownTypes = new List<Type>();
private const string sessionStateFilename = "_sessionState.xml";
/// <summary>
/// Provides access to global session state for the current session. This state is
/// serialized by <see cref="SaveAsync"/> and restored by
/// <see cref="RestoreAsync"/>, so values must be serializable by
/// <see cref="DataContractSerializer"/> and should be as compact as possible. Strings
/// and other self-contained data types are strongly recommended.
/// </summary>
public static Dictionary<string, object> SessionState
{
get { return _sessionState; }
}
/// <summary>
/// List of custom types provided to the <see cref="DataContractSerializer"/> when
/// reading and writing session state. Initially empty, additional types may be
/// added to customize the serialization process.
/// </summary>
public static List<Type> KnownTypes
{
get { return _knownTypes; }
}
/// <summary>
/// Save the current <see cref="SessionState"/>. Any <see cref="Frame"/> instances
/// registered with <see cref="RegisterFrame"/> will also preserve their current
/// navigation stack, which in turn gives their active <see cref="Page"/> an opportunity
/// to save its state.
/// </summary>
/// <returns>An asynchronous task that reflects when session state has been saved.</returns>
public static async Task SaveAsync()
{
try
{
// Save the navigation state for all registered frames
foreach (var weakFrameReference in _registeredFrames)
{
Frame frame;
if (weakFrameReference.TryGetTarget(out frame))
{
SaveFrameNavigationState(frame);
}
}
// Serialize the session state synchronously to avoid asynchronous access to shared
// state
MemoryStream sessionData = new MemoryStream();
DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<string, object>), _knownTypes);
serializer.WriteObject(sessionData, _sessionState);
// Get an output stream for the SessionState file and write the state asynchronously
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(sessionStateFilename, CreationCollisionOption.ReplaceExisting);
using (Stream fileStream = await file.OpenStreamForWriteAsync())
{
sessionData.Seek(0, SeekOrigin.Begin);
await sessionData.CopyToAsync(fileStream);
await fileStream.FlushAsync();
}
}
catch (Exception e)
{
throw new SuspensionManagerException(e);
}
}
/// <summary>
/// Restores previously saved <see cref="SessionState"/>. Any <see cref="Frame"/> instances
/// registered with <see cref="RegisterFrame"/> will also restore their prior navigation
/// state, which in turn gives their active <see cref="Page"/> an opportunity restore its
/// state.
/// </summary>
/// <returns>An asynchronous task that reflects when session state has been read. The
/// content of <see cref="SessionState"/> should not be relied upon until this task
/// completes.</returns>
public static async Task RestoreAsync()
{
_sessionState = new Dictionary<String, Object>();
try
{
// Get the input stream for the SessionState file
StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(sessionStateFilename);
using (IInputStream inStream = await file.OpenSequentialReadAsync())
{
// Deserialize the Session State
DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<string, object>), _knownTypes);
_sessionState = (Dictionary<string, object>)serializer.ReadObject(inStream.AsStreamForRead());
}
// Restore any registered frames to their saved state
foreach (var weakFrameReference in _registeredFrames)
{
Frame frame;
if (weakFrameReference.TryGetTarget(out frame))
{
frame.ClearValue(FrameSessionStateProperty);
RestoreFrameNavigationState(frame);
}
}
}
catch (Exception e)
{
throw new SuspensionManagerException(e);
}
}
private static DependencyProperty FrameSessionStateKeyProperty =
DependencyProperty.RegisterAttached("_FrameSessionStateKey", typeof(String), typeof(SuspensionManager), null);
private static DependencyProperty FrameSessionStateProperty =
DependencyProperty.RegisterAttached("_FrameSessionState", typeof(Dictionary<String, Object>), typeof(SuspensionManager), null);
private static List<WeakReference<Frame>> _registeredFrames = new List<WeakReference<Frame>>();
/// <summary>
/// Registers a <see cref="Frame"/> instance to allow its navigation history to be saved to
/// and restored from <see cref="SessionState"/>. Frames should be registered once
/// immediately after creation if they will participate in session state management. Upon
/// registration if state has already been restored for the specified key
/// the navigation history will immediately be restored. Subsequent invocations of
/// <see cref="RestoreAsync"/> will also restore navigation history.
/// </summary>
/// <param name="frame">An instance whose navigation history should be managed by
/// <see cref="SuspensionManager"/></param>
/// <param name="sessionStateKey">A unique key into <see cref="SessionState"/> used to
/// store navigation-related information.</param>
public static void RegisterFrame(Frame frame, String sessionStateKey)
{
if (frame.GetValue(FrameSessionStateKeyProperty) != null)
{
throw new InvalidOperationException("Frames can only be registered to one session state key");
}
if (frame.GetValue(FrameSessionStateProperty) != null)
{
throw new InvalidOperationException("Frames must be either be registered before accessing frame session state, or not registered at all");
}
// Use a dependency property to associate the session key with a frame, and keep a list of frames whose
// navigation state should be managed
frame.SetValue(FrameSessionStateKeyProperty, sessionStateKey);
_registeredFrames.Add(new WeakReference<Frame>(frame));
// Check to see if navigation state can be restored
RestoreFrameNavigationState(frame);
}
/// <summary>
/// Disassociates a <see cref="Frame"/> previously registered by <see cref="RegisterFrame"/>
/// from <see cref="SessionState"/>. Any navigation state previously captured will be
/// removed.
/// </summary>
/// <param name="frame">An instance whose navigation history should no longer be
/// managed.</param>
public static void UnregisterFrame(Frame frame)
{
// Remove session state and remove the frame from the list of frames whose navigation
// state will be saved (along with any weak references that are no longer reachable)
SessionState.Remove((String)frame.GetValue(FrameSessionStateKeyProperty));
_registeredFrames.RemoveAll((weakFrameReference) =>
{
Frame testFrame;
return !weakFrameReference.TryGetTarget(out testFrame) || testFrame == frame;
});
}
/// <summary>
/// Provides storage for session state associated with the specified <see cref="Frame"/>.
/// Frames that have been previously registered with <see cref="RegisterFrame"/> have
/// their session state saved and restored automatically as a part of the global
/// <see cref="SessionState"/>. Frames that are not registered have transient state
/// that can still be useful when restoring pages that have been discarded from the
/// navigation cache.
/// </summary>
/// <remarks>Apps may choose to rely on <see cref="LayoutAwarePage"/> to manage
/// page-specific state instead of working with frame session state directly.</remarks>
/// <param name="frame">The instance for which session state is desired.</param>
/// <returns>A collection of state subject to the same serialization mechanism as
/// <see cref="SessionState"/>.</returns>
public static Dictionary<String, Object> SessionStateForFrame(Frame frame)
{
var frameState = (Dictionary<String, Object>)frame.GetValue(FrameSessionStateProperty);
if (frameState == null)
{
var frameSessionKey = (String)frame.GetValue(FrameSessionStateKeyProperty);
if (frameSessionKey != null)
{
// Registered frames reflect the corresponding session state
if (!_sessionState.ContainsKey(frameSessionKey))
{
_sessionState[frameSessionKey] = new Dictionary<String, Object>();
}
frameState = (Dictionary<String, Object>)_sessionState[frameSessionKey];
}
else
{
// Frames that aren't registered have transient state
frameState = new Dictionary<String, Object>();
}
frame.SetValue(FrameSessionStateProperty, frameState);
}
return frameState;
}
private static void RestoreFrameNavigationState(Frame frame)
{
var frameState = SessionStateForFrame(frame);
if (frameState.ContainsKey("Navigation"))
{
frame.SetNavigationState((String)frameState["Navigation"]);
}
}
private static void SaveFrameNavigationState(Frame frame)
{
var frameState = SessionStateForFrame(frame);
frameState["Navigation"] = frame.GetNavigationState();
}
}
public class SuspensionManagerException : Exception
{
public SuspensionManagerException()
{
}
public SuspensionManagerException(Exception e)
: base("SuspensionManager failed", e)
{
}
}
}

Просмотреть файл

@ -0,0 +1,46 @@
using System;
using System.Windows.Input;
namespace BetterBindableBaseSample
{
public class DelegateCommand<T> : ICommand
{
private Action<T> _action;
private Func<T, bool> _canExecute;
public DelegateCommand(Action<T> action, Func<T, bool> canExecute = null)
{
_action = action;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecute != null ? _canExecute((T)parameter) : true;
}
public void Execute(object parameter)
{
_action((T)parameter);
}
public void RaiseCanExecuteChanged()
{
EventHandler handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
public sealed class DelegateCommand : DelegateCommand<object>
{
public DelegateCommand(Action action, Func<bool> canExecute = null)
: base(_ => action(), _ => canExecute != null ? canExecute() : true)
{
}
}
}

Просмотреть файл

@ -0,0 +1,128 @@
<common:LayoutAwarePage
x:Name="pageRoot"
x:Class="BetterBindableBaseSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BetterBindableBaseSample"
xmlns:common="using:BetterBindableBaseSample.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.DataContext>
<local:ViewModel/>
</Page.DataContext>
<Page.Resources>
<local:BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter"/>
<local:NegatedBooleanToVisibilityConverter x:Key="negatedBooleanToVisibilityConverter"/>
</Page.Resources>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Style="{StaticResource LayoutRootStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="140"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Back button and page title -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/>
<TextBlock x:Name="pageTitle" Grid.Column="1" Text="BetterBindableBase Sample" Style="{StaticResource PageHeaderTextStyle}"/>
</Grid>
<!-- Content -->
<StackPanel x:Name="contentStackPanel" Grid.Row="1" Margin="120,0,120,60">
<TextBlock Text="Network information" Style="{StaticResource PageSubheaderTextStyle}"/>
<StackPanel x:Name="nameStackPanel" Orientation="Horizontal">
<TextBlock Text="Connection name:" Style="{StaticResource BodyTextStyle}" FontSize="20"/>
<TextBlock Text="{Binding ConnectionName}" Style="{StaticResource TitleTextStyle}" FontSize="20" Margin="20,0,0,0"/>
</StackPanel>
<StackPanel x:Name="statusStackPanel" Orientation="Horizontal" Margin="0,20,0,0">
<TextBlock Text="Connection status:" Style="{StaticResource BodyTextStyle}" FontSize="20"/>
<TextBlock Text="{Binding ConnectionStatus}" Style="{StaticResource TitleTextStyle}" FontSize="20" Margin="20,0,0,0"/>
</StackPanel>
<StackPanel x:Name="costStackPanel" Orientation="Horizontal" Margin="0,20,0,0">
<TextBlock Text="Connection cost:" Style="{StaticResource BodyTextStyle}" FontSize="20"/>
<TextBlock Text="{Binding ConnectionCost.NetworkCostType}" Style="{StaticResource TitleTextStyle}" FontSize="20" Margin="20,0,0,0"/>
</StackPanel>
<StackPanel x:Name="receivedStackPanel" Orientation="Horizontal" Margin="0,20,0,0">
<TextBlock Text="Bytes received:" Style="{StaticResource BodyTextStyle}" FontSize="20"/>
<TextBlock Text="{Binding DataUsage.BytesReceived}" Style="{StaticResource TitleTextStyle}" FontSize="20" Margin="20,0,0,0"/>
</StackPanel>
<StackPanel x:Name="sentStackPanel" Orientation="Horizontal" Margin="0,20,0,0">
<TextBlock Text="Bytes sent:" Style="{StaticResource BodyTextStyle}" FontSize="20"/>
<TextBlock Text="{Binding DataUsage.BytesSent}" Style="{StaticResource TitleTextStyle}" FontSize="20" Margin="20,0,0,0"/>
</StackPanel>
<TextBlock Text="Calculation" Style="{StaticResource PageSubheaderTextStyle}" Margin="0,60,0,40"/>
<StackPanel x:Name="estimateStackPanel" Orientation="Horizontal">
<TextBlock Text="Estimate of π:" Style="{StaticResource BodyTextStyle}" FontSize="20"/>
<TextBlock Text="{Binding EstimateOfPi}" Style="{StaticResource TitleTextStyle}" FontSize="20" Margin="20,0,0,0"/>
</StackPanel>
<Button Content="Improve estimate" Command="{Binding ImproveEstimateCommand}" Margin="0,20,0,0"
Visibility="{Binding IsWorking, Converter={StaticResource negatedBooleanToVisibilityConverter}}"/>
<ProgressRing IsActive="True" Width="30" Height="30" HorizontalAlignment="Left" Margin="0,20,0,0"
Visibility="{Binding IsWorking, Converter={StaticResource booleanToVisibilityConverter}}"/>
</StackPanel>
<VisualStateManager.VisualStateGroups>
<!-- Visual states reflect the application's view state -->
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape"/>
<VisualState x:Name="Filled"/>
<!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
<VisualState x:Name="FullScreenPortrait">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PortraitBackButtonStyle}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!-- The back button and title have different styles when snapped -->
<VisualState x:Name="Snapped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedBackButtonStyle}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="pageTitle" Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedPageHeaderTextStyle}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentStackPanel" Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0" Value="20,0,20,60"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="nameStackPanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="statusStackPanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="costStackPanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="receivedStackPanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="sentStackPanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="estimateStackPanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</common:LayoutAwarePage>

Просмотреть файл

@ -0,0 +1,12 @@
using BetterBindableBaseSample.Common;
namespace BetterBindableBaseSample
{
public sealed partial class MainPage : LayoutAwarePage
{
public MainPage()
{
InitializeComponent();
}
}
}

Просмотреть файл

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/2010/manifest">
<Identity Name="4282dd35-0c77-4890-8c62-e4ad495d464d"
Publisher="CN=Beatriz"
Version="1.0.0.0" />
<Properties>
<DisplayName>BetterBindableBaseSample</DisplayName>
<PublisherDisplayName>Beatriz</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Prerequisites>
<OSMinVersion>6.2.1</OSMinVersion>
<OSMaxVersionTested>6.2.1</OSMaxVersionTested>
</Prerequisites>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="BetterBindableBaseSample.App">
<VisualElements
DisplayName="BetterBindableBaseSample"
Logo="Assets\Logo.png"
SmallLogo="Assets\SmallLogo.png"
Description="BetterBindableBaseSample"
ForegroundText="light"
BackgroundColor="#464646">
<DefaultTile ShowName="allLogos" />
<SplashScreen Image="Assets\SplashScreen.png" />
</VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
</Capabilities>
</Package>

Просмотреть файл

@ -0,0 +1,29 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("BetterBindableBaseSample")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("BetterBindableBaseSample")]
[assembly: AssemblyCopyright("Copyright © 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: ComVisible(false)]

Просмотреть файл

@ -0,0 +1,165 @@
using BetterBindableBaseSample.Common;
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.Networking.Connectivity;
namespace BetterBindableBaseSample
{
public sealed class ViewModel : BetterBindableBase
{
#region --- fields ---
private Random _random = new Random((int)DateTime.Now.Ticks);
private long _totalSampleCount;
private long _totalWithinCircleCount;
#endregion
#region --- constructor ---
public ViewModel()
{
NetworkInformation.NetworkStatusChanged += NetworkInformation_NetworkStatusChanged;
UpdateIsInternetConnected();
}
#endregion
#region --- network information ---
private string _connectionName;
public string ConnectionName
{
get { return _connectionName; }
set { SetProperty(ref _connectionName, value); }
}
private string _connectionStatus;
public string ConnectionStatus
{
get { return _connectionStatus; }
set { SetProperty(ref _connectionStatus, value); }
}
private ConnectionCost _connectionCost;
public ConnectionCost ConnectionCost
{
get { return _connectionCost; }
set { SetProperty(ref _connectionCost, value); }
}
private DataUsage _dataUsage;
public DataUsage DataUsage
{
get { return _dataUsage; }
set { SetProperty(ref _dataUsage, value); }
}
private void NetworkInformation_NetworkStatusChanged(object sender)
{
UpdateIsInternetConnected();
}
private void UpdateIsInternetConnected()
{
ConnectionProfile connectionProfile = NetworkInformation.GetInternetConnectionProfile();
if (connectionProfile != null)
{
ConnectionName = connectionProfile.ProfileName;
ConnectionCost = connectionProfile.GetConnectionCost();
DataUsage = connectionProfile.GetLocalUsage(DateTimeOffset.Now - TimeSpan.FromHours(24), DateTimeOffset.Now);
switch (connectionProfile.GetNetworkConnectivityLevel())
{
case NetworkConnectivityLevel.None:
ConnectionStatus = "None";
break;
case NetworkConnectivityLevel.LocalAccess:
ConnectionStatus = "Local access";
break;
case NetworkConnectivityLevel.ConstrainedInternetAccess:
ConnectionStatus = "Constrained internet access";
break;
case NetworkConnectivityLevel.InternetAccess:
ConnectionStatus = "Internet access";
break;
}
}
else
{
ConnectionName = "None";
ConnectionStatus = "None";
ConnectionCost = null;
DataUsage = null;
}
}
#endregion
#region --- calculation ---
public ICommand ImproveEstimateCommand
{
get
{
return new DelegateCommand(delegate
{
// Do work on a thread-pool thread,
// so it doesn't block the UI.
Task.Run(new Action(DoWork));
});
}
}
private bool _isWorking;
public bool IsWorking
{
get { return _isWorking; }
set { SetProperty(ref _isWorking, value); }
}
private double _estimateOfPi;
public double EstimateOfPi
{
get { return _estimateOfPi; }
set { SetProperty(ref _estimateOfPi, value); }
}
private void DoWork()
{
IsWorking = true;
// Iterate as many times as we can in 3 seconds.
const double workIntervalInSeconds = 3;
long withinCircleCount = 0;
long sampleCount = 0;
DateTime startTime = DateTime.Now;
while ((DateTime.Now - startTime).TotalSeconds < workIntervalInSeconds)
{
// Count the number of random points in the unit square
// that fall within the unit circle.
double x = _random.NextDouble();
double y = _random.NextDouble();
double radiusSquared = x * x + y * y;
bool isWithinCircle = radiusSquared <= 1;
if (isWithinCircle)
{
withinCircleCount++;
}
sampleCount++;
}
// Add our counts to the totals.
_totalSampleCount += sampleCount;
_totalWithinCircleCount += withinCircleCount;
// Update our estimate of pi.
EstimateOfPi = 4 * (double)_totalWithinCircleCount / (double)_totalSampleCount;
IsWorking = false;
}
#endregion
}
}

Двоичные данные
78-BetterBindableBase/Images/78BetterBindableBase.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 31 KiB

Просмотреть файл

@ -0,0 +1,82 @@
# A better BindableBase
Visual Studio has some great features built into its project templates for Windows Store applications. In particular, the Grid App (XAML) and Split App (XAML) project templates include the BindableBase class, which serves as a convenient starting point when building view models for data binding. The BindableBase class provides an implementation of INotifyPropertyChanged, along with a convenient SetProperty method that makes it easy to add properties to your view model. Getting property change notifications to work correctly with data binding is as simple as adding a field and a property that follow this pattern:
public class ViewModel : BindableBase
{
private string _connectionName;
public string ConnectionName
{
get { return _connectionName; }
set { SetProperty(ref _connectionName, value); }
}
}
Or so it might seem.
Suppose our view model listens for changes in network connectivity:
public ViewModel()
{
NetworkInformation.NetworkStatusChanged +=
NetworkInformation_NetworkStatusChanged;
}
private void NetworkInformation_NetworkStatusChanged(object sender)
{
ConnectionProfile connectionProfile =
NetworkInformation.GetInternetConnectionProfile();
ConnectionName = connectionProfile.ProfileName;
}
If we now bind to the ConnectionName property, we'd expect to see the name update whenever we connect or disconnect an internet-connected ethernet or wireless network. Unfortunately, the UI doesn't update, and instead we see a COMException in BindableBase's OnPropertyChanged method while debugging. The exception message gives a good hint about what went wrong:
The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
A little investigation reveals that the system uses a worker thread to monitor network connections and to fire NetworkStatusChanged events, but data bindings refuse to accept property change events unless they are fired on the UI thread. Windows Store apps share this restriction with Silverlight and WinForms. (WPF is more forgiving, automatically marshalling property change events across threads for you.) Depending how your Windows Store app code is structured, a property change notification issued by a worker thread may result in premature termination of the worker thread.
One possible solution is to change the handler of the NetworkStatusChanged event so that it relies on the UI thread to update the view model. Assuming we have access to the CoreDispatcher object of the UI thread, we'd end up with code like this:
private void NetworkInformation_NetworkStatusChanged(object sender)
{
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
ConnectionProfile connectionProfile =
NetworkInformation.GetInternetConnectionProfile();
ConnectionName = connectionProfile.ProfileName;
});
}
This approach works, but it's not very elegant to sprinkle "Dispatcher.RunAsync" calls in all the places where view model code may be called by worker threads. Instead, we would much rather change the code in just the one place that actually raises the PropertyChanged event. That's why we created the BetterBindableBase class. It's identical to the BindableBase class except in its constructor (which looks for the current UI dispatcher) and its implementation of OnPropertyChanged (which uses the dispatcher to make sure we're running on the UI thread before raising the PropertyChanged event):
protected BetterBindableBase()
{
if (!DesignMode.DesignModeEnabled)
{
_dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
}
}
protected void OnPropertyChanged([CallerMemberName]
string propertyName = null)
{
if (_dispatcher == null || _dispatcher.HasThreadAccess)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this,
new PropertyChangedEventArgs(propertyName));
}
}
else
{
IAsyncAction doNotAwait =
_dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => OnPropertyChanged(propertyName));
}
}
Our sample app contains a view model that listens to NetworkStatusChanged events, and also performs some mathematical calculations on a thread-pool thread. When the view model derives from BindableBase, the UI doesn't update properly for either the network status changes or the calculations. When the view model derives from BetterBindableBase, things work properly.
<img src="Images/78BetterBindableBase.png" class="postImage" />