Ich bin gerade dabei alle Blog Einträge auf WinRT und Windows 8 RP zu aktualisieren. Das Gruppieren wurde anhand einer Listview und CollectionViewSource gezeigt. Nun folgt Version zwei mit weniger VB.NET Code. Für die Gruppierung muss eine CollectionViewSource zwischen geschaltet werden die im XAML im Resources Bereich deklariert wurde. <CollectionViewSource x:Name="cvs1"
IsSourceGrouped="true" ItemsPath="Items"
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Ganz wichtig ist das Attribut isSourceGrouped und der Verweis auf das Items.
Um diese Gruppe zu erzeugen wird ein LINQ Statement zwischen geschaltet. Dort gibt es die Möglichkeit zu gruppieren mit Group by.
Dim pers = New personen
Dim q = From p In pers
Group p By key = p.sex Into Group
Select New With {.key = key, .Items = Group}
cvs1.Source = q
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Hier fallt natürlich auf, das die erzeugten Daten mit den Ursprungsdaten wenig gemein haben. Die Gruppe wird in einer generischen Liste in der Eigenschaft Group zwischen abgelegt und dann sozusagen im selben Atemzug den Items als neuer anonymer Typ zugewiesen.
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Der XAML Code benötigt ein Template für die Gruppe und eines für die Listen Einträge. Im Headertemplate wird aus dem true oder false des Key per Converter ein Text.
<ListView ItemsSource="{Binding Source={StaticResource cvs1}}" >
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="7,7,0,0">
<Button Content="{Binding key,Converter={StaticResource fmConverter}}" />
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding name}" FontSize="20"
Margin="10" FontWeight="Bold"/>
<TextBlock Text="{Binding alter}" FontSize="20"
Margin="10" FontWeight="Bold"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Wenn man sich mitteilen möchte muss auch ein Empfänger bereit stehen. In WinRT nennt sich das Share Target. Eine METRO App kann Ziel für bestimmte Typen oder alles sein. Festgelegt wird dies im Application Manifest.
Um sich Aufwand zu sparen empfehle ich den Wizard von Visual Studio 2012 zu verwenden. Dieser erzeugt ein neues Item im VS Projekt vom Typ Share Target Contract.
Ich finde die Bezeichnung im METRO Projekt eigentlich falsch. Es wird eigentlich eine XAML Page erzeugt, die aufgerufen wird, wenn jemand das Share aus einer anderen APP aufruft. Die Verbindung zwischen dieser XAML Seite und dem Share wird vom Wizard im app.xaml.vb Event ShareTargetActivated hergestellt.
Protected Overrides Sub OnShareTargetActivated(ByVal e As Windows.ApplicationModel.Activation.ShareTargetActivatedEventArgs)
Dim shareTargetPage = New ShareTargetPage1
shareTargetPage.Activate(e)
End Sub
Das Manifest (Package.appxmanifest) legt fest, wann die eigene Anwendung im Charm als Target aufscheint. Auch das erledigt der Projekt Template Wizward.
Aus der Windows 8 Foto Anwendung kann dann, in die METRO Mail App geteilt werden.
Der Benutzer wählt aus den möglichen Apps aus und erhält dann den Dialog der vorhin mit dem Namen ShareTargetPage1 erzeugt wurde.
Diese XAML Seite und den VB.NET Code wird man dann modifizieren, so das er aussieht wie in der Mail APP. Es wird also statt der APP die Share Target Seite gestartet. Diese überlagert funktioniell wie ein modaler Popup Dialog, die aktuelle APP zu ca 45% von rechts.
Dies geschieht im Activated Event, das durch den Wizard schon mit VB.NET Code vorbelegt ist.
Public Async Sub Activate(args As ShareTargetActivatedEventArgs)
Me._shareOperation = args.ShareOperation
' Communicate metadata about the shared content through the view model
Dim shareProperties = Me._shareOperation.Data.Properties
Dim thumbnailImage = New BitmapImage()
Me.DefaultViewModel("Title") = shareProperties.Title
Me.DefaultViewModel("Description") = shareProperties.Description
Me.DefaultViewModel("Image") = thumbnailImage
Me.DefaultViewModel("Sharing") = False
Me.DefaultViewModel("ShowImage") = False
Window.Current.Content = Me
Window.Current.Activate()….
Wenn man das Konzept ein wenig studiert, stolpert man auch noch über LayoutAwarePage im Verzeichnis Common die von Page erbt und eine ObservableDictionary als MiniViewModel implementiert. Von dieser Seite erbt dann die Share Target Page und weist wie oben im VB.NET Code Beispiel die Werte als Key Value der Page zu.
In meinem Versuchen bleibt das Search Target Fenster nicht aktiv bzw verschwindet sofort wieder wenn der Visual Studio debugger attached ist.
Aktualisiert auf Windows 8 RP
Zwei Windows 8.1 Stores Apps ( METRO APP) tauschen über Contracts Daten aus. Eine APP ist Source und eine Target. Dabei ist es natürlich egal, mit welcher Sprache C#, VB.NET, C++ oder JavaScript die Anwendung programmiert wurde. Relevanter ist schon welche Art von Information man teilen möchte. Das muss vom Typ zusammen passen. Möglich ist vieles wie Text, Bilder oder Links. In diesem Blog Beitrag werden wir eine Anwendung beschreiben, die Ihre Daten teilt. Einfacher Text per Share Charm an eine andere Anwendung. Die Klasse DataTransferManager liefert den Context, Methoden und Events. Da jede Page innerhalb einer APP meist etwas anderes zu sharen hat, üblicherweise im Pageload. 1: Dim dm As DataTransferManager 2: Private Sub Page_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded 3: dm = DataTransferManager.GetForCurrentView() 4: End Sub
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Es gibt ein spezielles Event DataRequested das ausgelöst wird, wenn per Charm vom Benutzer das Share initiiert wird. Das Event muss abonniert werden um es dann behandeln zu können. Dort werden dann die Daten zugewiesen.
1: AddHandler dm.DataRequested, AddressOf reqHandler
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Alternativ kann der Sharing Context UI der WinRT API auch per Code angezeigt werden.
1: DataTransferManager.ShowShareUI
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
In der Ereignis Behandlung wird in den Argumenten eine Referenz auf das angeforderte Share Objekt übergeben. Eigenschaften wie Titel und Beschreibung müssen dann per VB.NET Code zugewiesen werden. Andere Methoden beziehen sich spezifisch auf den Inhalt. Wie SetBitmap, SetRtf oder SetData für HTML. Neu in Windows 8.1 sind z.B. SetWebLink oder SetApplicationLink und ersetzen damit setURI (obsolet).
1: Private Sub reqHandler(sender As DataTransferManager, args As DataRequestedEventArgs) 2: args.Request.Data.Properties.Title = "dataPackageTitle" 3: args.Request.Data.Properties.Description = "dataPackageDescription" 4: args.Request.Data.SetText("dataPackageText") 5: End Sub
Windows 8.1 erlaubt nun ein Sharing aus jeder APP. Es wird einfach der Screenshot angeboten.
Die vollständige Doku findet sich in der MSDN online.
Im folgenden VB.NET Beispiel wird ein fehlerhafter Share simuliert. Der Benutzer sieht dann beim nächsten Aufruf des Charm die Fehlermeldung. Dies wird benötigt um in der Source App den Share per Programmierlogik abbrechen zu können.
1: Private Sub errorHandler(sender As DataTransferManager, args As DataRequestedEventArgs) 2: args.Request.Data.Properties.Title = "dataPackageTitleError" 3: args.Request.Data.Properties.Description = "dataPackageDescriptionError" 4: args.Request.FailWithDisplayText("so nicht ganz boeser Fehler") 5: End Sub 6: Private Sub Button_set(sender As Object, e As RoutedEventArgs) 7: AddHandler dm.DataRequested, AddressOf reqHandler 8: DataTransferManager.ShowShareUI() 9: 10: End Sub
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Wer mit HTML zu tun hat, kennt das mailto:info@ppedv.de?subject=hallo. Beim Click auf den Hyperlink öffnet sich das Mail Programm und die To und Betreffzeile ist gefüllt. Auch ein anderes Protokoll nämlich http://www.ppedv.de kennt man, der Browser öffnet sich und zeigt die Website an. In Windows 8 kann man nun eigene Protokolle definieren. In Vorbereitung eines Fax Clients wird es pp-fax werden. Der erste Weg führt, wie so oft in WinRT in das Manifest der Anwendung. Hier der Screenshot von Visual Studio 11 Als nächstes wird in der app.xaml.vb das entsprechende Event OnActivated überladen. Im Event wird dann einen Seite,in dem Fall einfach ein Usercontrol instanziiert und die Eventargs übergeben. darin befinden sich nämlich das wichtigste Attribut, die ursprüngliche URI. Protected Overrides Sub OnActivated(ByVal args As IActivatedEventArgs)
Dim pwindow As New protAct
pwindow.pargs = args
Window.Current.Content = pwindow
Window.Current.Activate()
End Sub
Wenn der Benutzer in der Explorer Leiste dann eingibt wie folgt. pp-fax://meinehost/irgendwas?faxnummer=23235345345 eingibt wird die METRO App gestartet. Internet Explorer geht natürlich auch.
Jetzt kann in der Seite die Information ausgewertet werden. Dieses VB Beispiel legt dafür einfach ein Property an vom Typ ProtocolActivatedEventArgs. Im Setter werden dann Textboxen befühlt. Die Klasse URI stellt eine Reihe praktischer Helfer bereit um die URL in Ihre Bestandteile Host, Pfad und Querystring zu zerlegen.
Private _pargs As ProtocolActivatedEventArgs
Public Property pargs() As ProtocolActivatedEventArgs
Get
l Return _pargs
End Get
Set(ByVal value As ProtocolActivatedEventArgs)
_pargs = value
textbox2.Text = _pargs.Uri.Host
textbox3.Text = _pargs.Uri.Query
'pargs.Uri.AbsolutePath
'_pargs.Uri.Segments()
End Set
End Property
In der vorliegenden Windows 8 Consumer Preview, ist es nicht möglich per HyperlinkButton die Protokoll Aktivierung einer fremden METRO App zu erzwingen. Allerdings klappt es mit der Launcher Klasse eine externe Anwendung zu starten.
Private Async Function Button_Click_2(sender As Object, e As RoutedEventArgs) As Task
Await Launcher.LaunchUriAsync(New Uri("mailto:INFO@ppedv.de?subject=hannes"))
End Function
Nein es geht nicht um Frührente. der Lebenszyklus von Windows 8 METRO Anwendungen etwas eigenwillig. Kurz gesagt, kann man das Betriebssystem einen in den Hintergrund gelegte Anwendung aus dem Betriebssystem Sheduler entfernen. Der Modus nennt sich suspended. warum und wieso dauert etwas länger zu erklären. Relevant ist, was man aus Coding Sicht tun muss. Eigentlich muss man gar nichts tun. Die Anwendung wird ja nicht geschlossen, insofern kommt sie in dem Status zurück wie man sie zuletzt gesehen hat. Eine Textbox beinhaltet noch immer den Text. Aber Windows 8 hat das rechteine Suspended APP komplett zu entfernen. Ich habe das zwar noch nie gesehen, aber bei kommenden ARM Geräten mit wenig Arbeitsspeicher wird das wohl passieren. Sämtliche Beispiele aus dem SDK enthalten eine Hilfsklasse die sich SuspensionManager nennt.Ich kopiere die aktuell in meine Projekte, auch wenn sie nicht optimal ist. Module SuspensionManager
Private sessionState_ As New Dictionary(Of String, Object)
Private knownTypes_ As List(Of Type) = New List(Of Type)
Private Const filename As String = "_sessionState.xml"
' Provides access to the currect session state
Public ReadOnly Property SessionState As Dictionary(Of String, Object)
Get
Return sessionState_
End Get
End Property
' Allows custom types to be added to the list of types that can be serialized
Public ReadOnly Property KnownTypes As List(Of Type)
Get
Return knownTypes_
End Get
End Property
' Save the current session state
Public Async Function SaveAsync() As Task
' Get the output stream for the SessionState file.
Dim file As StorageFile = Await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting)
Dim raStream As IRandomAccessStream = Await file.OpenAsync(FileAccessMode.ReadWrite)
Using outStream As IOutputStream = raStream.GetOutputStreamAt(0)
' Serialize the Session State.
Dim serializer As New DataContractSerializer(GetType(Dictionary(Of String, Object)))
serializer.WriteObject(outStream.AsStreamForWrite, sessionState_)
Await outStream.FlushAsync
End Using
End Function
' Restore the saved sesison state
Public Async Function RestoreAsync() As Task
' Get the input stream for the SessionState file.
Try
Dim file As StorageFile = Await ApplicationData.Current.LocalFolder.GetFileAsync(filename)
If file Is Nothing Then
Exit Function
End If
Dim inStream As IInputStream = Await file.OpenSequentialReadAsync
' Deserialize the Session State.
Dim serializer As New DataContractSerializer(GetType(Dictionary(Of String, Object)))
sessionState_ = CType(serializer.ReadObject(inStream.AsStreamForRead), Dictionary(Of String, Object))
Catch ex As Exception
' Restoring state is best-effort. If it fails, the app will just come up with a new session.
End Try
End Function
End Module
In der Applikation Klasse app.xaml.vb wird dann diese aufgerufen wenn die Anwendung schlafen geht oder wieder aufwacht.
Protected Async Sub OnSuspending(ByVal sender As Object, ByVal args As SuspendingEventArgs) Handles Me.Suspending
Dim deferral As SuspendingDeferral = args.SuspendingOperation.GetDeferral
Await SuspensionManager.SaveAsync
deferral.Complete()
End Sub
Protected Overrides Async Sub OnLaunched(ByVal args As LaunchActivatedEventArgs)
If args.PreviousExecutionState = ApplicationExecutionState.Terminated Then
' Do a synchronous restore
Await SuspensionManager.RestoreAsync
End If
Spannend ist der Part in dem man per Getdeferral WinRT bittet die Anwendung vom Suspending zu verschonen. Schließlich kann das speichern ja etwas länger dauern und damit wird der 5 Sekunden Zyklus unterbrochen.
Ob die Anwendung vom Suspended oder Terminated Modus kommt, spielt für das Restore der Settings natürlich eine Rolle.
Im eigentlichen VB.NET Programmcode wird auch der Suspensionmanager um einer Art Session wie in ASP.NET relevanten Infos abzulegen. So kann das ausgewählte Listenelement wieder hergestellt werden.
If SuspensionManager.SessionState.ContainsKey("SelectedScenario") Then
Dim selectedScenarioName As String = TryCast(SuspensionManager.SessionState("SelectedScenario"), String)
startingScenario = TryCast(Me.FindName(selectedScenarioName), ListBoxItem)
End If
Vermutlich wird es zwei Wege geben. Schlichtweg ignorieren oder Design Orgien mit Expression Blend. Windows 8 Anwendungen können in mindestens vier verschiedenen Layouts daher kommen. Relativ klar ist Hochformat oder Querformat. Man kann in Visual Studio 11 die METRO styled APP auch dazu zwingen in einem bestimmten Format zu starten. Dazu kommt noch der jeweilige Flipped Mode der quasi das Bild um 180 Grad dreht und so den physikalischen Windows 8 Start Button nach oben bringt. Je nach Anwendungstyp kann es sinnvoll sein sich als Entwickler vorher festzulegen. Typische Reader werden meist im Hochformat arbeiten. Videos wird man eher quer betrachten. Dann sind da noch die flexiblen Anwendungen die alles können. Die Weather APP wird gerne als Beispiel genannt. Es gibt Fullscreen (ganzer Bildschirm), Snapped (Randdbereich) und Filled(das dazwischen. Je in der Landscape und Portrait Variante. Das erkennen des aktuellen Zustandes gestaltet WinRT noch einfach Private Sub UserControl36_Loaded(sender As Object, e As RoutedEventArgs)
Handles Me.Loaded
AddHandler ViewManagement.ApplicationView.GetForCurrentView().ViewStateChanged,
AddressOf gedreht
End Sub
Private Async Function gedreht(sender As ApplicationView,
args As ApplicationViewStateChangedEventArgs) As Task
Dim msg = New MessageDialog(args.ViewState.ToString)
Await msg.ShowAsync
End Function
Wie aber gestaltet man den XAML Code dazu? Die Beispiele arbeiten alle mit Viewstates. Es findet sich folgender VB.NET Beispiel Code dazu um den State zu wechseln.
Private Sub MainPage_ViewStateChanged(ByVal sender As ViewManagement.ApplicationView,
ByVal args As Windows.UI.ViewManagement.ApplicationViewStateChangedEventArgs)
Select Case args.ViewState
Case Windows.UI.ViewManagement.ApplicationViewState.Filled
VisualStateManager.GoToState(Me, "Fill", False)
Case Windows.UI.ViewManagement.ApplicationViewState.FullScreenPortrait
VisualStateManager.GoToState(Me, "FullScreenPortrait", False)
Case Windows.UI.ViewManagement.ApplicationViewState.FullScreenLandscape
VisualStateManager.GoToState(Me, "FullScreenLandscape", False)
Case Windows.UI.ViewManagement.ApplicationViewState.Snapped
VisualStateManager.GoToState(Me, "Snapped", False)
Case Else
End Select
End Sub
Visual Studio 11 kann zwar exzellent XAML editieren, aber keine Animationen und damit auch Viewstates. Wenn man das nicht in XAML runtertippen möchte bleibt nur Expression Blend.
Dort kann man für jeden Status Animationen definieren. Diese können Controls auf dem Userinterface bewegen, drehen oder auch schmäler machen. Man kann auch komplette Controls ein und Ausblenden und so aus einem Grid eine Listview machen. Bisher kenne ich keine Anwendung die das konsequent umsetzt.
So wird beim Kalender aus einer Tagesansicht
in der snapped Darstellung eine Monats Übersicht,
Auch wenn der Visual State Manager von Expression Blend hilft, ein vollständiger Designer für die Sichten ist er nicht. So gehe ich davon aus das die ersten Windows 8 METRO Anwendungen aus dem Store alle nur auf eine Sicht hin optimiert sind.
Nur wenige APPs werden auch im ersten Schritt die Notwendigkeit haben sozusagen aktiv auf der Seite zu laufen, gerade weil auch über Toasts Anwendungen im Suspended Modus sich beim Benutzer melden können.
Durch Zufall bin ich darüber gestolpert. In Windows 8 Projekten legt Visual Studio 11 im Common Verzeichnis eine Klasse BindableBase an. Diese kapselt, auch dank eines neuen Attributes CallerMemberName in .NET 4.5, sehr elegant den Code für die Implementierung des Event Systems. Dies ist nötig um den View, als das per XAML definierte Userinterface, über Änderungen an den Daten zu informieren. Der bisherige Ansatz implementiert das Interface INotifyPropertyChanged in der Viewmodell Klasse. Dann wird das Event per RaiseEvent für jede Eigenschaft einzeln gefeuert. Aber nur wenn die Daten sich auch geändert haben. Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
Implements INotifyPropertyChanged.PropertyChanged
Private _BenutzerName As String
Public Property BenutzerName() As String
Get
Return _BenutzerName
End Get
Set(ByVal value As String)
If _BenutzerName <> value Then
_BenutzerName = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("BenutzerName"))
End If
End Set
End Property
End Class
Auch wenn das Raisevent in der Praxis meist in eine Methode OnPropertyChanged gekapselt wird, noch ganz schien viel Code.
Nun zum Vergleich der ungleich schönere Code mit der geerbten BindableBase Implementierung. Vor allem die unschöne und von Intellisense nicht unterstützte Schreibweise mit dem PropertyName als String fällt weg.
Public Class PersonViewModel
Inherits BindableBase
Private _BenutzerName As String
Public Property BenutzerName() As String
Get
Return _BenutzerName
End Get
Set(ByVal value As String)
_BenutzerName = value
SetProperty(_BenutzerName, value)
End Set
End Property
Das VB.NET Codebeispiel wurde in WinRT geschrieben, sollte auch in .NET 4.5 Windows Desktop Anwendungen funktionieren.
Unter dem Motto alles ganz anders und doch ähnlich, steht das speichern von Settings mit WinRT. ganz ähnlich wie bei Silverlight kann man einzelne Werte in ein Key Value Dictionary anlegen oder direkt komplexe Datentypen serialisieren und per Dateizugriff lesen und schreiben. Zusätzlich kann man aber nun die Dictionarys in Container gruppieren. Vermutlich um Gruppen von Einstellungen leichter handhaben zu können. Der Große Unterschied ist, das es kein IsolatedStorage gibt. Die Daten werden entweder lokal, Temporär oder Roaming und damit in der Cloud gespeichert. Dim localSettings As Windows.Storage.ApplicationDataContainer = Windows.Storage.ApplicationData.Current.LocalSettings
Dim temporaryFolder As Windows.Storage.StorageFolder = Windows.Storage.ApplicationData.Current.TemporaryFolder
Dim roamingSettings As Windows.Storage.ApplicationDataContainer = Windows.Storage.ApplicationData.Current.RoamingSettings
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Speichern eines Wertes per Key lokal
localSettings.Values("hannes") = textbox1.Text + " " + Date.Now
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Gruppieren per Container
localSettings.CreateContainer("hannescontainer", ApplicationDataCreateDisposition.Always)
localSettings.Containers("hannesontainer").Values("eins") = textbox1.Text + " " + Date.Now
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Speichern im Temp Folder ( bei mir C:\Users\pre\AppData\Local\Packages\BUILD.588a3b42-fdba-460e-8a17-f527a66ad183_89gf582k2a27c\TempState )
Dim tf As StorageFile = Await temporaryFolder.CreateFileAsync("hannes.txt",
CreationCollisionOption.ReplaceExisting)
Await FileIO.WriteTextAsync(tf, textbox1.Text + " " + Date.Now)
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Bisher konnte ich nicht herausfinden wann der Inhalt des Tempfolders wieder geleert wird.
Die letzte Möglichkeit ist das Speichern in der Cloud, als Roaming bezeichnet. Damit kann der Benutzer von einem Gerät zum anderen wechseln und dort weiter arbeiten. Um das Änderungen in der Konfiguration mitzubekommen gibt es auch ein Event DataChanged (Windows.Storage.ApplicationData.Current.DataChanged ).
Mit folgendem VB.NET Code Beispiel wird gesichert
roamingSettings.Values("hannesroaming") = textbox1.Text + " " + Date.Now
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
In der Microsoft Cloud gibt es natürlich auch Beschränkungen, die man per Code auslesen kann.
' textblock1.Text = ApplicationData.Current.RoamingStorageQuota
textblock1.Text = ApplicationData.Current.RoamingFolder.Path
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Das speichern erledigt WinRT allerdings asynchron. Zuerst werden die Settings in der normalen Settings.dat Datei lokal gespeichert. Erst nach einigen Sekunden, funkt Windows 8 zu Microsoft und übergibt in einem komprimierten XML Format die Daten, wie der Screenshot mit Fiddler zeigt.
Ohne es zu wissen, kann das Roaming nur über die Windows ID (Live ID) funktionieren. Bei meinem Samsung XE700 melde ich mich mit der Live ID an. Wie das ist mit einer Domain Anmeldung, habe ich noch nicht getestet und auch nicht in der Doku gefunden.
Wenn man in Windows 8 Tiles oder Popups Bilder anzeigen möchte stellen sich mehrere Fragen. Zunächst das Format. Möglich ist png und jpg (auch jpeg). Auch Transparenz in PNG funktioniert. Typischerweise wird ein Bild in das APPX Paket eingepackt und damit in die Visual Studio 11 Solution. In Silverlight Anwendungen war das eher verpönt, weil der Download eine Rolle gespielt hat.Bei WinRT wird die Anwendung ohnehin dauerhaft installiert und damit das oder die Images. Der Eigenschaftsdialog muss dann das Bild als Content zeigen. Dann lässt sich im Image Source Attribut über die neuen Protokoll Spezifikationen ms-appx auf das Bild verweisen. In diesem Fall liegt das Bild im Unterverzeichnis Images. <Image HorizontalAlignment="Left" Height="100" Margin="36,39,0,0" VerticalAlignment="Top" Width="347" Source="ms-appx:///Images/ppedv.jpg"/>
Wenn Bild Link fehlerhaft ist erscheint keine Fehlermeldung.
Die zweite Möglichkeit ist, das Bild lokal auf der Festplatte in den Settings der Anwendung zu speichern (oder Roaming, dazu aber wann anders mehr). Der folgende Code liest das Verzeichnis aus.
Dim localSettings As Windows.Storage.ApplicationDataContainer = Windows.Storage.ApplicationData.Current.LocalSettingsDim localFolder As Windows.Storage.StorageFolder = Windows.Storage.ApplicationData.Current.LocalFolder
Textbox1.Text = localFolder.Path
Am Ende kommt ein bekannter Pfad raus, der den Namen des Pakets aus den Visual Studio appx Manifest enthält plus einen Salt Wert.
C:\Users\pre\AppData\Local\Packages\BUILD.588a3b42-fdba-460e-8a17-f527a66ad183_89gf582k2a27c\LocalState
Ich habe eine Grafik Datei per Hand dort hin kopiert. Referenziert wird diese mit einer weiteren WinRT Protokoll Spezifikation mx-appdata und dem fixen Zusatz local.
<Image HorizontalAlignment="Left"
VerticalAlignment="Top" Source="ms-appdata:///local/windows.png"/>
Natürlich kann man das Bild auch aus dem Web laden mit dem HTTP Protokoll.
Entgegen meiner Erwartungen wird das Bild auch gecacht. Konkret per Etag und 304 Meldung, so das die Anwendung dauerhaft dasselbe Bild nicht.
<Image HorizontalAlignment="Left"
VerticalAlignment="Top" Source="http://www.firmenpresse.de/adpics/76898.jpg?id=2212"/>
Das Caching lässt sich nicht beeinflussen Abhilfe schafft ein alter Trick mit einem angehängten wechselnden Querystring.
Wenn sich der Inhalt einer Kachel (also dem Menüpunkt) ändert, spricht man von einer Live Tile. Der Internet Explorer 10 Metro Styled nutzt das um Websites in das METRO Menü zu bringen. In der Appbar einfach Pin auswählen und schon kann die Seite in Zukunft vom Benutzer direkt angesprungen werden. Wenn die Webseite ein Icon (32x32) enthält, wird dieses dann in die Kachel eingebaut. Außerdem kann man die Beschriftung per Metatag vorbelegen. Der Benutzer kann den Titel aber immer anpassen. <head runat="server"><title></title> <link href="favicon.ico" rel="shortcut icon" /> <meta name="application-name" content="ppedv Demo" /> <meta name="msapplication-badge" content="frequency=30;polling-uri=http://localhost/badge.aspx" />
</head>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
In der letzten Zeile der Metatags (Badge) kann man sogar ein aktives Element definieren. Das heißt eine andere Webseite liefert einen WinRT Badge typischen XML String. In diesem Beispiel verwende ich eine komplett entkernte ASPX Datei. Es sind wirklich nur die beiden Zeilen XML bzw. VB.NET Code enthalten.
<?xml version="1.0" encoding="utf-8" ?>
<badge value="<%=cint(rnd(1)*100) %>"/>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Die Wiederholintervall ist Minuten, also in dem Fall 30 Minuten. Wenn der Badge Value auf unavailable gesetzt wird erscheint ein kleines rundes Bullet Icon im Tile. Gültige Werte sind laut Doku 30, 60, 360, 720, 1440 (default=1 Tag)
Wenn es Neuigkeiten gibt zeigt die Kachel dies dann an. Hier ist die Kachel auch gleich markiert.
Damit kann man in der Appbar dann auch das “Live” an und abschalten.
Theoretisch sollten man den Update auch per Javascript ausführen können. Die nötige Methode window.external.msSiteModeRefreshBadge(); funktioniert leider bei mir nicht.