Win10 UWP開發系列:實現Master/Detail佈局

来源:http://www.cnblogs.com/yanxiaodi/archive/2016/02/04/5181195.html
-Advertisement-
Play Games

在開發XX新聞的過程中,UI部分使用了Master/Detail(大綱/細節)佈局樣式。Win10系統中的郵件App就是這種樣式,左側一個列表,右側是詳情頁面。關於這種 樣式的說明可參看MSDN文檔:https://msdn.microsoft.com/zh-cn/library/windows/a


在開發XX新聞的過程中,UI部分使用了Master/Detail(大綱/細節)佈局樣式。Win10系統中的郵件App就是這種樣式,左側一個列表,右側是詳情頁面。關於這種 樣式的說明可參看MSDN文檔:https://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/dn997765.aspx

樣式如下:

在微軟官方的Sample里,有這種樣式的代碼示例,下載地址:https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/XamlMasterDetail

這個例子可以拿來直接用。處理這種佈局,主要是需要處理在PC/Mobile不同屏幕寬度下的具體顯示內容,可以使用VisualState來實現不同狀態的切換。

為了將WP8.1版本的項目快速升級到UWP版本,我沒有使用官方示例的方式,而是在MainPage里放了兩個Frame,在左側的Frame里放一個列表Page,右側Frame里放一個詳情Page,這樣之前的頁面的UI和ViewModel都可以原封不動的拿過來,只需要單獨處理VisualState的切換就可以了。

下麵以一個簡單的例子來說一下是如何實現的。最近關於Win10 UWP的內容寫了不少,都是在開發的過程中,把能單獨拿出來的部分再重新做一遍demo,所以如果大家有興趣的話可以照著動手敲一遍,自己實現出來才會理解的更深入。

一、新建項目及Model

首先新建一個MVVM-Sidekick項目,命名為MasterDetailDemo。

添加Models目錄,新建一個NewsItem:

public class NewsItem
{
public int Id { get; set; }
public DateTime DateCreated { get; set; }
public string Title { get; set; }
public string Text { get; set; }
}

 

 

新建一個ItemsDataSource類,用於模擬數據,可以返回一些數據。具體代碼看Demo里的。

private static List<NewsItem> _items = new List<NewsItem>()

{

new NewsItem()
{}……
}

public static IList<NewsItem> GetAllItems()
{
return _items;
}

public static NewsItem GetItemById(int id)
{
return _items[id];
}

 

 

二、頁面佈局

在MainPage中放置一個Grid控制項,分為兩列,左側和右側分別放兩個Frame控制項:

<Grid x:Name="gridMain" >
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="0" />
    </Grid.ColumnDefinitions>
    <Frame x:Name="masterFrame" Grid.Column="0" mvvm:StageManager.Beacon="masterFrame" x:FieldModifier="public"/>
    <Frame x:Name="detailFrame" HorizontalAlignment="Stretch" Grid.Column="1" mvvm:StageManager.Beacon="detailFrame" x:FieldModifier="public">
    </Frame>
</Grid>            

 

 

然後添加兩個頁面,MasterPage和DetailPage。

MasterPage中放置一個ListView控制項,調用剛纔的ItemsDataSource類,把數據綁定到ListView上,這個就不用詳述了吧。還要給ListView設置項模板。這部分代碼就不貼了。

現在讓MainPage頁面載入時,左側的Frame自動顯示MasterPage。

打開MainPage_Model.cs文件,取消對OnBindedViewLoad方法的註釋,修改為以下代碼:

protected override async Task OnBindedViewLoad(MVVMSidekick.Views.IView view)
{
    await base.OnBindedViewLoad(view);
    await StageManager["masterFrame"].Show(new MasterPage_Model());

}

 

 

好了,現在當MainPage頁面載入完成後,名為masterFrame的Frame會顯示MasterPage的內容,像下麵這樣:

然後要實現點擊項的時候,要在右側的Frame里顯示DetailPage。

打開MasterPage.xaml,在頭部引入以下幾個命名空間:

xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Behaviors="using:MVVMSidekick.Behaviors"

 

然後修改項模板,使用SendToEventRouterAction,這個東東在以前的Blog里說過,在項模板的Grid里添加以下代碼:

<Interactivity:Interaction.Behaviors>
    <Core:EventTriggerBehavior EventName="Tapped">
        <Behaviors:SendToEventRouterAction EventRoutingName="NewsItemTapped" EventData="{Binding}" IsEventFiringToAllBaseClassesChannels="True" />
    </Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>    

 

 

在MainPage載入的時候,註冊NewsItemTapped事件,來處理點擊事件。打開MainPage_Model.cs文件,在最後添加一個RegisterCommand方法:

private void RegisterCommand()
{
     MVVMSidekick.EventRouting.EventRouter.Instance.GetEventChannel<Object>()
.Where(x => x.EventName == "NewsItemTapped")
.Subscribe(
e =>
    {
        NewsItem item = e.EventData as NewsItem;
        await StageManager["detailFrame"].Show(new DetailPage_Model(item));
    }
    ).DisposeWith(this);
}    

 

別忘了在Loaded事件里調用這個方法:

protected override async Task OnBindedViewLoad(MVVMSidekick.Views.IView view)
{
    this.RegisterCommand();
    await base.OnBindedViewLoad(view);
    await StageManager["masterFrame"].Show(new MasterPage_Model());
}

 

因為DetailPage_Model還沒有可接收參數的構造函數,所以需要在DetailPage_Model裡加兩個構造函數,一個是無參的,一個是可接收參數的,同時還需要加一個可綁定的屬性,用來顯示內容:

public DetailPage_Model()
{ }
 

public DetailPage_Model(NewsItem item)
{
    CurrentNewsItem = item;
}



public NewsItem CurrentNewsItem
{
get { return _CurrentNewsItemLocator(this).Value; }
set { _CurrentNewsItemLocator(this).SetValueAndTryNotify(value); }
}

#region Property NewsItem CurrentNewsItem Setup

protected Property<NewsItem> _CurrentNewsItem = new Property<NewsItem> { LocatorFunc = _CurrentNewsItemLocator };

static Func<BindableBase, ValueContainer<NewsItem>> _CurrentNewsItemLocator = RegisterContainerLocator<NewsItem>("CurrentNewsItem", model => model.Initialize("CurrentNewsItem", ref model._CurrentNewsItem, ref _CurrentNewsItemLocator, _CurrentNewsItemDefaultValueFactory));

static Func<NewsItem> _CurrentNewsItemDefaultValueFactory = () => { return default(NewsItem); };

#endregion

 

這樣在DetailPage里就可以接收到點擊的是哪個NewsItem了,再綁定到界面上,我就隨便放了個TextBlock:

<StackPanel x:Name="RootPanel" Grid.Row="1">
<TextBlock
Margin="8,0"
Style="{ThemeResource TitleTextBlockStyle}"
HorizontalAlignment="Left"
Text="{Binding CurrentNewsItem.Title}" />
<TextBlock
Margin="12,8"
HorizontalAlignment="Left"
MaxWidth="560"
Style="{ThemeResource BodyTextBlockStyle}"
Text="{Binding CurrentNewsItem.Text}"
EntranceNavigationTransitionInfo.IsTargetElement="True" />
</StackPanel>

 

運行一下看看,怎麼點了沒反應呢,原來在MainPage的Grid里,第一列就把寬度占滿了,第二列無法顯示了,來給兩個列設置個寬度吧,第一列設置為2*,第二列設置為3*:

現在可以顯示了:

三、自定義StateTrigger

但是,這隻是第一步,接下來需要處理在不同屏幕寬度下的適配問題,我們可以打開UWP版的郵件,拖動視窗縮放大小,觀察頁面內容變化,可以得出以下特性:

在PC上:

1、當寬度大於一定寬度時,Master和Detail是可以同時顯示的,在剛打開程式沒有點擊郵件的時候,右側的Detail實際上顯示了一個空頁面(有背景圖片);

當逐步縮小寬度時,又分為兩種情況:

2、如果Detail頁為空頁面時,縮小到一定寬度後,視窗只顯示Master頁面;

3、如果Detail頁不為空,即顯示郵件正文的時候,縮小到一定寬度後,視窗只顯示Detail頁面;

在Mobile上:

程式打開時,顯示Master頁面,相當於2;

點擊郵件後,顯示Detail頁面,相當於3;

這樣我們可以得出,不管是PC還是Mobile,需要處理三種狀態的切換,我命名為:

NarrowAndBlankDetail

NarrowAndNoBlankDetail

Wide

通過處理這三種狀態的切換,就可以實現類似郵件UWP版的效果了。有些同學可能會問,為什麼不直接使用自帶的AdaptiveTrigger呢,主要是這個AdaptiveTrigger只能根據寬度來設置,而目前的需求還需要根據Detail頁面是否為空來處理,所以需要自定義一個Trigger了。

郵件UWP預設載入的時候有一個空頁面,所以還需要添加一個BlankPage,這個頁面相當於一個空頁面,裡面可以隨便放點什麼東西,比如背景圖片啊,logo啊,或者廣告什麼的,Trigger會根據Detail頁面是否顯示這個BlankPage來進行處理。

先讓MainPage載入時,預設左側載入MasterPage,右側載入BlankPage:

protected override async Task OnBindedViewLoad(MVVMSidekick.Views.IView view)
{
    this.RegisterCommand();
    await base.OnBindedViewLoad(view);
    StageManager["detailFrame"].Show(new BlankPage_Model());
    await StageManager["masterFrame"].Show(new MasterPage_Model());
}

 

好,運行效果是這樣:

現在來處理狀態切換。關於StateTrigger,國外已經有人寫了一個項目,實現了多種Trigger,見:

https://github.com/dotMorten/WindowsStateTriggers

使用介紹見:http://www.sharpgis.net/post/2015/03/24/Using-Custom-Visual-State-Triggers

這個項目實現了n個實用的Trigger,但好可惜沒有能滿足我的要求的,還是自己動手吧。我參考了他的代碼,繼承了他的介面ITriggerValue,繼承此介面的話可以用在CompositeStateTrigger里,為了方便以後使用按照這個介面來吧。

說一下主要的代碼實現思路。

首先要定義一個枚舉:

public enum MasterDetailState
{
/// <summary>
/// narrow and a blank detail page
/// </summary>
NarrowAndBlankDetail,

/// <summary>
/// narrow and detail page is not blank
/// </summary>
NarrowAndNoBlankDetail,

/// <summary>
/// wide
/// </summary>
Wide
}

 

頁面寬度的變化,通過訂閱ApplicationView.GetForCurrentView().VisibleBoundsChanged事件來處理,如果寬度大於720時如何,小於720時如何。當然這個720也可以傳遞屬性進來,我懶得弄就寫死在裡面了。

同時,還需要一個DetailContent屬性,這個屬性需要綁定到第二個Frame的Content上,這樣Trigger可以知道當前是不是BlankPage,通過以下代碼來判斷:

MVVMPage detailPage = (MVVMSidekick.Views.MVVMPage)DetailContent;
if (detailPage != null)
{
if (detailPage.BaseUri.ToString() == "ms-appx:///BlankPage.xaml")
{
System.Diagnostics.Debug.WriteLine("觸發NarrowAndBlankDetail模式");
//CommonContext.Instance.CurrentBackRequestedHandlerType = BackRequestedHandlerType.MasterPage;
return MasterDetailState.NarrowAndBlankDetail;
}
else
{
System.Diagnostics.Debug.WriteLine("觸發NarrowAndNoBlankDetail模式");
//CommonContext.Instance.CurrentBackRequestedHandlerType = BackRequestedHandlerType.DetailPage;
return MasterDetailState.NarrowAndNoBlankDetail;
}
}

 

全部代碼如下:

public class MasterDetailStateTrigger : StateTriggerBase, ITriggerValue

{

 

 

public MasterDetailStateTrigger()

{

if (!Windows.ApplicationModel.DesignMode.DesignModeEnabled)

{

 

var weakEvent =

new WeakEventListener<MasterDetailStateTrigger, ApplicationView, object>(this)

{

OnEventAction = (instance, source, eventArgs) => MasterDetailStatetateTrigger_MasterDetailStateChanged(source, eventArgs),

OnDetachAction = (instance, weakEventListener) => ApplicationView.GetForCurrentView().VisibleBoundsChanged -= weakEventListener.OnEvent

};

ApplicationView.GetForCurrentView().VisibleBoundsChanged += weakEvent.OnEvent;

 

}

}

 

private void MasterDetailStatetateTrigger_MasterDetailStateChanged(ApplicationView sender, object args)

{

UpdateTrigger();

}

 

private void UpdateTrigger()

{

IsActive = GetMasterDetailState() == MasterDetailState;

 

}

 

 

 

 

 

 

public MasterDetailState MasterDetailState

{

get { return (MasterDetailState)GetValue(MasterDetailStateProperty); }

set { SetValue(MasterDetailStateProperty, value); }

}

 

// Using a DependencyProperty as the backing store for MasterDetailState. This enables animation, styling, binding, etc...

public static readonly DependencyProperty MasterDetailStateProperty =

DependencyProperty.Register("MasterDetailState", typeof(MasterDetailState), typeof(MasterDetailStateTrigger), new PropertyMetadata(MasterDetailState.Wide, OnMasterDetailStatePropertyChanged));

 

private static void OnMasterDetailStatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

 

var obj = (MasterDetailStateTrigger)d;

if (!Windows.ApplicationModel.DesignMode.DesignModeEnabled)

{

obj.UpdateTrigger();

}

}

 

 

 

 

 

public object DetailContent

{

get { return (object)GetValue(DetailContentProperty); }

set { SetValue(DetailContentProperty, value); }

}

 

// Using a DependencyProperty as the backing store for DetailContent. This enables animation, styling, binding, etc...

public static readonly DependencyProperty DetailContentProperty =

DependencyProperty.Register("DetailContent", typeof(object), typeof(MasterDetailStateTrigger), new PropertyMetadata(null, new PropertyChangedCallback(OnValuePropertyChanged)));

 

private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

var obj = (MasterDetailStateTrigger)d;

obj.UpdateTrigger();

}

 

 

 

 

internal MasterDetailState GetMasterDetailState()

{

System.Diagnostics.Debug.WriteLine("DetailContent為空:" + (DetailContent == null).ToString());

//第一種 窄屏模式 DetailFrame為空

if (Window.Current.Bounds.Width < 720)

{

System.Diagnostics.Debug.WriteLine("VisibleBounds.Width:" + ApplicationView.GetForCurrentView().VisibleBounds.Width.ToString());

System.Diagnostics.Debug.WriteLine("Window.Current.Bounds:" + Window.Current.Bounds.Width.ToString());

MVVMPage detailPage = (MVVMSidekick.Views.MVVMPage)DetailContent;

if (detailPage != null)

{

if (detailPage.BaseUri.ToString() == "ms-appx:///BlankPage.xaml")

{

System.Diagnostics.Debug.WriteLine("觸發NarrowAndBlankDetail模式");

return MasterDetailState.NarrowAndBlankDetail;

}

else

{

System.Diagnostics.Debug.WriteLine("觸發NarrowAndNoBlankDetail模式");

return MasterDetailState.NarrowAndNoBlankDetail;

}

}

else

{

return MasterDetailState.NarrowAndBlankDetail;

}

}

else

{

System.Diagnostics.Debug.WriteLine("觸發Wide模式");

return MasterDetailState.Wide;

}

}

 

 

 

#region ITriggerValue

 

private bool m_IsActive;

 

/// <summary>

/// Gets a value indicating whether this trigger is active.

/// </summary>

/// <value><c>true</c> if this trigger is active; otherwise, <c>false</c>.</value>

public bool IsActive

{

get { return m_IsActive; }

private set

{

if (m_IsActive != value)

{

m_IsActive = value;

base.SetActive(value);

if (IsActiveChanged != null)

IsActiveChanged(this, EventArgs.Empty);

}

}

}

 

/// <summary>

/// Occurs when the <see cref="IsActive" /> property has changed.

/// </summary>

public event EventHandler IsActiveChanged;

 

#endregion ITriggerValue

 

 

 

}

 

public enum MasterDetailState

{

/// <summary>

/// narrow and a blank detail page

/// </summary>

NarrowAndBlankDetail,

/// <summary>

/// narrow and detail page is not blank

/// </summary>

NarrowAndNoBlankDetail,

/// <summary>

/// wide

/// </summary>

Wide

}
View Code

 

我在代碼里輸出了一些信息,調試的時候可以觀察各種狀態是在什麼時候切換的。

然後在MainPage.xaml里 應用這個StateTrigger,首先,要在MainPage的ViewModel里添加一個object,用於綁定DetailFrame的內容:

/// <summary>
/// detailFrame的內容
/// </summary>
public object DetailContent
{
get { return _DetailContentLocator(this).Value; }
set { _DetailContentLocator(this).SetValueAndTryNotify(value); }
}

#region Property object DetailContent Setup

protected Property<object> _DetailContent = new Property<object> { LocatorFunc = _DetailContentLocator };

static Func<BindableBase, ValueContainer<object>> _DetailContentLocator = RegisterContainerLocator<object>("DetailContent", model => model.Initialize("DetailContent", ref model._DetailContent, ref _DetailContentLocator, _DetailContentDefaultValueFactory));

static Func<object> _DetailContentDefaultValueFactory = () => default(object);

#endregion

 

MainPage.xaml里的第二個Frame的Content綁定到這個DetailContent上:

<Frame x:Name="detailFrame" Content="{Binding DetailContent,Mode=TwoWay}" HorizontalAlignment="Stretch" Grid.Column="1" mvvm:StageManager.Beacon="detailFrame" x:FieldModifier="public">

 

註意Mode要設置為TwoWay,這樣才可以讓Trigger知道DetaiFrame的內容。在MainPage.xaml的根Grid里添加以下Trigger:

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup>
        <VisualState x:Name="NarrowAndBlankDetail">
            <VisualState.StateTriggers>
                <triggers:MasterDetailStateTrigger MasterDetailState="NarrowAndBlankDetail" DetailContent="{Binding DetailContent}" />
            </VisualState.StateTriggers>
        <VisualState.Setters>
            <Setter Target="gridMain.ColumnDefinitions[0].Width" Value="*" />
            <Setter Target="gridMain.ColumnDefinitions[1].Width" Value="0" />
        </VisualState.Setters>
    </VisualState>
    <VisualState x:Name="NarrowAndNoBlankDetail">
        <VisualState.StateTriggers>
            <triggers:MasterDetailStateTrigger MasterDetailState="NarrowAndNoBlankDetail" DetailContent="{Binding DetailContent}" />
           </VisualState.StateTriggers>
        <VisualState.Setters>
            <Setter Target="gridMain.ColumnDefinitions[0].Width" Value="0" />
            <Setter Target="gridMain.ColumnDefinitions[1].Width" Value="*" />
        </VisualState.Setters>
        </VisualState>
        <VisualState x:Name="Wide">
            <VisualState.StateTriggers>
                <triggers:MasterDetailStateTrigger MasterDetailState="Wide" DetailContent="{Binding DetailContent}" />
            </VisualState.StateTriggers>
        <VisualState.Setters>
            <Setter Target="gridMain.ColumnDefinitions[0].Width" Value="2*" />
            <Setter Target="gridMain.ColumnDefinitions[1].Width" Value="3*" />
        </VisualState.Setters>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>    

 

還要把預設的gridMain的兩列的寬度預設值分別改為*和0:

<Grid x:Name="gridMain" >
    <Grid.RenderTransform>
        <CompositeTransform />
    </Grid.RenderTransform>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="0" />
    </Grid.ColumnDefinitions>
    <Frame x:Name="masterFrame" Grid.Column="0" mvvm:StageManager.Beacon="masterFrame" x:FieldModifier="public"/>
    <Frame x:Name="detailFrame" Content="{Binding DetailContent,Mode=TwoWay}" HorizontalAlignment="Stretch" Grid.Column="1" mvvm:StageManager.Beacon="detailFrame" x:FieldModifier="public">
</Frame>

</Grid>

 

Trigger的意義很清楚了,Setter會根據不同的狀態去設置gridMain兩列的寬度來控制MasterPage和DetailPage的顯示和隱藏:

當剛開始進入程式,左側顯示列表,右側顯示BlankPage,這時候如果寬度大於720,兩個頁面正常展示,如果頁面寬度小於720,則只顯示列表頁;

如果頁面寬度大於720的時候,點擊列表,右側正常顯示詳情;

如果頁面寬度小於720,點擊列表,列表會隱藏,只顯示詳情;

基本達到了文章開頭提出的目的。

四、處理返回鍵

當在手機上運行的時候,就會發現當點擊列表顯示DetailPage後,再按返回鍵直接退出程式了。因為還沒有處理返回鍵事件。PC上也一樣,程式左上角應該有個返回按鈕。下麵來處理返回事件。

基本思路是,點擊返回後,應該先判斷DetailPage是否可GoBack,如果可以就GoBack,直到返回最開始的BlankPage為止,這樣StateTrigger會自動觸發NarrowAndBlankDetail狀態,顯示MasterPage。

返回是處理SystemNavigationManager.GetForCurrentView().BackRequested這個事件,打開MainPage.xaml.cs文件,在OnNavigatedTo里訂閱這個事件:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    SystemNavigationManager.GetForCurrentView().BackRequested += CurrentView_BackRequested;
        //SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
    base.OnNavigatedTo(e);
}

 

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    SystemNavigationManager.GetForCurrentView().BackRequested -= CurrentView_BackRequested;
    base.OnNavigatedFrom(e);
}

 

private void CurrentView_BackRequested(object sender, BackRequestedEventArgs e)
{
    //判斷DetailPage能否GoBack,如果可以GoBack則GoBack 顯示BlankPage
    //其次判斷MasterPage能否GoBack,如果可以GoBack則GoBack
    //如果不能GoBack,則提示是否退出
    if     (StrongTypeViewModel.StageManager["detailFrame"].CanGoBack)
    {
        e.Handled = true;
        StrongTypeViewModel.StageManager["detailFrame"].Frame.GoBack();
    }
    else if (StrongTypeViewModel.StageManager["masterFrame"].CanGoBack)
    {
        e.Handled = true;
        StrongTypeViewModel.StageManager["masterFrame"].Frame.GoBack();
    }
    else
    {
        //TODO 隱藏回退鍵 提示退出
        SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Collapsed;
    }
}

 

用戶點擊返回鍵的時候,首先看DetailPage能否GoBack,再看MasterPage能否GoBack,當沒有可GoBack的時候就把返回鍵隱藏。

在PC上的返回鍵預設是隱藏的,還需要在導航到詳情頁的時候將其展示出來,修改MainPage_Model.cs文件里的RegisterCommand方法:

private void RegisterCommand()
{
MVVMSidekick.EventRouting.EventRouter.Instance.GetEventChannel<Object>()
.Where(x => x.EventName == "NewsItemTapped")
.Subscribe(
async e =>
{
NewsItem item = e.EventData as NewsItem;
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
await StageManager["detailFrame"].Show(new DetailPage_Model(item));
}
).DisposeWith(this);
}

 

現在運行一下,PC上也可以返回了。當第一次打開的時候,是這樣 的:

如果拖動視窗縮小,則只會顯示MasterPage:

當點擊列表項時,會只顯示DetailPage:

點擊左上角返回鍵,又只顯示MasterPage了。

具體切換動畫我不會截圖,大家可以下載demo自己試試。

五、添加切換動畫效果

我們還可以做的更美觀一點。UWP預設的Page切換是有動畫效果的,但這裡因為只使用StateTrigger設置了Grid的列寬,當從DetailPage返回MasterPage的時候MasterPage一下子就顯示出來了,感覺有點生硬。現在給切換加一個動畫。

在NarrowAndBlankDetail的VisualState里,添加一段StoryBoard:

<Storyboard >
    <DoubleAnimation Storyboard.TargetName="gridMain" Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:0.6">
        <DoubleAnimation.EasingFunction>
            <CircleEase EasingMode="EaseOut" />
        </DoubleAnimation.EasingFunction>
    </DoubleAnimation>
    <DoubleAnimation Storyboard.TargetName="gridMain" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" From="-100" To="0" Duration="0:0:0.3">
        <DoubleAnimation.EasingFunction>
            <CircleEase EasingMode="EaseOut" />
        </DoubleAnimation.EasingFunction>
    </DoubleAnimation>
</Storyboard>

 

設置透明度從0到1,同時有一個移動的效果。註意這裡的StoryBoard.TargetProperty的寫法,詳細說明可以參考MSDN文檔:

https://msdn.microsoft.com/zh-cn/library/windows/apps/windows.ui.xaml.media.animation.storyboard.targetproperty.aspx

https://msdn.microsoft.com/zh-cn/library/windows/apps/jj569302.aspx

再次吐槽一下MSDN文檔真是太難找了。版本太多。

在<VisualStateManager.VisualStateGroups>里添加Transitions:

<VisualStateGroup.Transitions>
    <VisualTransition From="NarrowAndNoBlankDetail" To="NarrowAndBlankDetail" ></VisualTransition>
</VisualStateGroup.Transitions>

 

同時要在gridMain里添加以下代碼:

<Grid.RenderTransform>
    <CompositeTransform />
</Grid.RenderTransfor

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 由於網速和大中華區域網效果,使得我們在DockerHub下載鏡像的速度很慢,甚至一些國內的鏡像倉庫,也感覺速度不是很好。所以,很有必要在本地或者一個我們訪問很快速的地方(自己的雲伺服器)搭建一套鏡像倉庫。有了這樣一個倉庫,不僅可以提高下載速度,而且可以增加我們個性化定製的鏡像,以備後續使用。這篇將介
  • linux centos 6.5 _64 oracle 11.2g 今天接到一個客戶電話說重啟了伺服器,資料庫沒有重啟來,看了資料庫的報錯 沒有找到control文件的路徑,資料庫啟動到了 unmount狀態 , 因為資料庫是在存儲上放著的,資料庫程式安裝在本機上 , df -h 查看存儲沒有哦掛載
  • 一、分散式緩存簡圖 二、為什麼使用Memcached分散式緩存呢? 三、Memcached基礎原理 四、Memcache下載與安裝 五、MencacheHelper.cs 示例使用 結合Session與項目配置緩存 六、Redis和Memcache的區別總結 一、分散式緩存簡圖 二、為什麼使用Mem
  • 最小數據集(Minimum Data Set,MDS)最小數據集的概念起源於美國的醫療領域,用來統一醫療賬單.最小數據集是指通過收集最少的數據,最好地掌握一個研究對象所具有的特點或一件事情、一份工作所處的狀態,其核心是針對被觀察的對象建立一套精簡實用的數據指標.最小數據集的出現,最早是因為不同組織之
  • 什麼是c#預處理指令?? 用於在 C# 源代碼中嵌入的編譯器命令。 C#預處理器指令有哪些?? ↓↓↓這些就是預處理器指令啦 下麵我們一一道來(●'◡'●) 1.#if ,#elif,#else,endif c#編譯的第一步就是預處理,這一步中,根據源程式中#開頭的指令(預處理指令)進行處理。 例如
  • Asp.Net Filter學習,學習了基本過濾器、ActionFilterAttribute以及異常過濾器 當然最後學習了全局異常過濾器 文中有很多拍不足之處,敬請各位諒解
  • 1.0啟用開發者模式 ①填寫伺服器配置: 啟用開發模式需要先成為開發者,而且編輯模式和開發模式只能選擇一個(進入微信公眾平臺=>開發=>基本配置)就可以看到以下的界面: 點擊修改配置,會出現以下界面: 填寫伺服器地址(URL)、Token和EncodingAESKey,其中URL是開發者用來接收微信
  • 最近看到不少介紹微軟ASP.NET Identity技術的文章,但感覺都不夠完整深入,本人又恰好曾在Adam Freeman所著的《Pro ASP.NET MVC Platform》一書中看到過有關ASP.NET Identity的完整介紹,為此特將有關章節翻譯出來,希望需要瞭解此項技術的園友能從中...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...