6. díl - Programujeme pro Windows 8 - Práce se soubory

C# .NET Windows Store Aplikace Programujeme pro Windows 8 - Práce se soubory

V minulém díle jsme si ukázali jakým způsobem obsluhovat základní Life-Cycle aplikace. V dnešním díle si ukážeme několik způsobů jak číst a zapisovat data do souborů.

Každá aplikace může zasahovat pouze do předem určených složek. Nemůžeme si vynutit uložení nějakého souboru mimo vyhrazené složky bez vědomí uživatele. Pokud budeme chtít uložit soubor mimo tyto speciální složky, musíme využít tzv. Pickery (viz. níže).

Složky

Ještě jednou složky, kam můžeme zasahovat bez vědomí uživatele. Složky jsou také označovány jako Isolated Storage, tedy Izolované úložiště.

  • Local Folder - složka pro soubory (.txt,.xml,obrazové soubory,...)
  • Roaming Folder - to samé jako Local s tím rozdílem, že tato složka se automaticky synchronizuje přes SkyDrive pokud ho má uživatel aktivní
  • Temp Folder - Složka pro dočasné soubory. Soubory po znovu otevření aplikace zmizí.
  • Local Settings - Místo pro ukládání různého nastavení. Je to datový kontejner. K hodnotám přistupujeme přes Klíče. Tedy Klíč - Hodnota
  • Roaming Settings - To samé jako Local Settings a opět synchronizování přes SkyDrive

Následující složky známe i z dřívějších systémů a ve WinRT jsou označovány jako KnownFolders

  • Documents - Složka určená pro dokumenty
  • Pictures - Složka pro obrázky
  • Videos - Složka pro video
  • Music - Složka pro audio

Pozn. Koho by zajímalo kde se Local / Roaming složky nacházejí tak je to zde: C:\Users\Jmeno­Uzivatele\Local\Pac­kages\APLIKACE. V packages mají aplikace jméno podle klíče. Musíte holt hledat :)

Uložení

Pojďme si tedy něco uložit. Uložíme pouze .txt soubor ve formátu CSV. Budeme mít databázi uživatelů (viz tento článek http://www.itnetwork.cz/…-souboru-csv od sdraco). Nebudu tu řešit žádný návrh aplikace a objektů. Předpokládám, že máme tedy List<Uzivatel> uzivatele. Každý má jméno, věk a datum registrace. Metoda pro uložení může vypadat následovně

public async Task<bool> UlozUzivatele()
{
        //zíká složku Local Folder
        StorageFolder folder = ApplicationData.Current.LocalFolder;

        //vytovří nový soubor asynchronně
        StorageFile file = await folder.CreateFileAsync("uzivatele.txt");

        try
        {
                // načte stream
                Stream stream = await file.OpenStreamForWriteAsync();

                //založí StreamWriter ze stream
                /*
                 * Můžeme místo StreamWriter použít i FileIO a metodu jako WriteTextAsync
                 * */
                using (StreamWriter writer = new StreamWriter(stream))
                {
                        //Zapíšeme uživatele
                        foreach (Uzivatel u in uzivatele)
                        {
                                await writer.WriteLineAsync(string.Format("{0},{1},{2}", u.Jmeno, u.Vek, u.Registrovan.ToString("d.M.y")));
                        }
                }
                await stream.FlushAsync();
                return true;
        }
        catch
        {
                return false;
        }
}

Metoda je celkem prostá. Získáme složku, založíme soubor. Ze souboru vytvoříme stream a zapíšeme text. Stream poté uzavřeme. Určitě Vás ale zaráží jedna věc. Klíčová slova jako async a await už jsme jednou použili a opět vás zklamu. Dnes si to ještě nevysvětlíme. Berte to jako kouzelné slovíčko, které nám zajistí asynchronní běh metod. Celou metodu kvůli používání asynchroních metod StreamWriteru, Streamu apod musíme označit jako async! Tím říkáme, že zakládáme nové vlákno a kdekoliv je await, hlavní vlákno s UI běží (reaguje) stále dál a v pozadí se dějí další výpočty.

Především v prostředí Windows 8 aplikací se snažte o asynchronní přístup. Aplikace neprojde ani certifikací pokud vám hlavní vlákno s UI zamrzne!

Pokud async metoda potřebuje něco vracet, musí vracet objekt typu Task<T> nebo void.

Pokud chceme RoamingFolder, jednoduše načteme Roaming Folder a postup je stejný. Načítání myslím není potřeba ukazovat. Místo StreamWriter bychom použili StreamReader a ze složky bychom načetli soubor přes GetFileAsync(). Samozřejmě místo StreamWriteru / Readeru je možnost použít statickou třídu FileIO. Záleží na situaci a potřeby uložení.

XML Serializace

Formát CSV se hodí opravdu na pár skromných řádků dat. Mnohem lepší a přehlednější je XML formát. Máme zde stále možnost použít XmlReader / Writer. My si ukážeme jednodušší a rychlejší způsob, pokud chceme načíst vše a víme co přesně načítáme.

Hlavní třídou, která bude potřeba, je XmlSerializer. Třída která představuje data musí mít vždy konstruktor bez parametrů a samozřejmě klidně další s parametry. Skromná třída:

public class User
{
   public string Name{get;set;}
   public int Age{get;set;}

   private User()
   {}

  public User(string name,int age)
  {
    this.Name = name;
    this.Age = age;
  }
}

Data:

List<User> users = new List<User>(){
            new User("Pepa",15),
            new User("Vašek",25),
            new User("Jarmila",15)
        };

A kód pro serializaci a uložení

public async Task<bool> Uloz()
{
        try
        {
                StringWriter w = new StringWriter();
                //Načteme složku
                StorageFolder folder = ApplicationData.Current.RoamingFolder;
                //Vytvoříme soubor a v případě existence přemažeme
                StorageFile file = await folder.CreateFileAsync("notes.xml", CreationCollisionOption.ReplaceExisting);
                //Založíme Serializer typu List<User>
                XmlSerializer srl = new XmlSerializer(typeof(List<User>));

                //serializujeme
                srl.Serialize(w, users);

                //zapíšeme přes FileIO
                await FileIO.WriteTextAsync(file, w.ToString());
                // zavřeme StreamWriter
                await w.FlushAsync();

                return true;
        }
        catch
        {
                return false;
        }
}

Opět myslím, že kód mluví za vše :)

Obdobně bude načtení:

public async Task<bool> Nacti()
{
        try
        {
                // načteme složku
                StorageFolder folder = ApplicationData.Current.RoamingFolder;
                //načteme soubor
                StorageFile file  = await folder.GetFileAsync("users.xml");
                //založíme Serializer
                XmlSerializer srl = new XmlSerializer(typeof(List<User>));

                // přeteme text ze souboru (ještě stále XML formát)
                string text = await FileIO.ReadTextAsync(file);

                // založíme XmlReader
                XmlReader r = XmlReader.Create(new StringReader(text));
                //Deserializujeme a přetypujeme
                users = (List<User>)srl.Deserialize(r);

                return true;
        }
        catch
        {
                return false;
        }
}

Vidíme, že práce s XML je velmi snadná pokud nepotřebujeme vytáhnout určité části XML :)

Pickery

Picker je (s trochou nepřesnosti) komponenta, která umožňuje uživateli uložit / načíst soubor nebo otevřít složku pro čtení. Je to také jediný správný postup jak uložit data mimo určené složky. Uživatel vždy musí vědět co se mu děje na zařízení (PC,tablet, ...).

Pickerů máme tedy 3 typy

  • FileOpenPicker - načte jeden nebo více souborů
  • FileSavePicker - uloží jeden nebo více souborů do nastavené cesty
  • FolderPicker - umožní otevřít složku uvnitř aplikace

U FileOpenPicker a FolderPicker nalezneme jednu důležitou vlastnost a tím je FileTypeFilter. Jedná se o kolekci List<string> a udává jaký typ souborů můžeme otevřít / uložit. Pokud chceme všechny typy přidáme do kolekce hvězdičku() nebo určíme přesně typy souborů a to uvedením přípony: .txt,.xtml,.png,­... Obdobně u *FileSavePicker nalezneme vlastnost FileTypeChoices která udává co můžeme zvolit.

Ukázka Pickeru, který načte zvolený soubor.

FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add(".txt");
StorageFile file = await picker.PickSingleFileAsync();
IList<string> result = await FileIO.ReadLinesAsync(file, UnicodeEncoding.Utf8);

Pickery dokáží uložit i více souborů najednou. Určitě se podívejte na další nastavení Pickerů. Lze je různě stylovat :)

Settings

LocalSettings nebo RoamingSettings je speciální kontejner, kde můžeme snadno uchovávat hodnoty podle klíče. Je to jakýsi trvalý Dictionary. Myslím, že není zde co víc uvádět.

// uložení
ApplicationData.Current.LocalSettings.Values["name"] = "Zirko";

//načtení
string name = ApplicationData.Current.LocalSettings.Values["name"].ToString();

KnownFolders

Načtení a ukládání do KnownFolders je stejně jednoduché jako ostatní. Například Videos

StorageFolder videosFolder = KnownFolders.VideosLibrary;

Capabilities

S KnownFolders a s mnoha dalšími věcmi (v budoucnu si řekneme více) souvisí Package.appxma­nifest. V prvním díle jsme si o tomto souboru něco málo řekli. Pokud využíváte KnownFolders, musíte zaškrtnout tu library, kterou používáte a přidat deklarace. Ovšem už překračujeme dnešní náplň článku. Detailněji si to popíšeme jindy.

Dnešní článek byl velmi teoretický, příště se vrhneme na něco zajímavějšího :)


 

  Aktivity (1)

Článek pro vás napsal Petr Nymsa
Avatar
Autor se věnuje programování v C# a celkově se zajímá o .NET technologie. Působí jako Microsoft Student Partner.

Jak se ti líbí článek?
Celkem (5 hlasů) :
4444 4


 



 

 

Komentáře
Zobrazit starší komentáře (7)

Avatar
Petr Nymsa
Redaktor
Avatar
Petr Nymsa:

Metodu ve které voláš Uloz označ jako async a ulož metodu zavolej s await. Tím docílíš oddělení nového vlákna a "čekáš" na dokončení uložení. Také lze pustit tuto metodu přes třídu Task.Run

Odpovědět  +1 26.1.2014 12:22
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
KopRus
Neregistrovaný
Avatar
KopRus:

Musím to dělat špatně, protože se mi nevytvořil xml soubor... zkouším to takhle: Třída

public class User
{
    private User users;
    public string Name { get; set; }
    public int Age { get; set; }

    public User()
    {

    }

    public User(string name, int age)
    {
        this.Name = name;
        this.Age = age;

    }



    public async Task<bool> Uloz()
    {
        try
        {
            StringWriter w = new StringWriter();
            //Načteme složku
            StorageFolder folder = ApplicationData.Current.RoamingFolder;
            //Vytvoříme soubor a v případě existence přemažeme
            StorageFile file = await folder.CreateFileAsync("notes.xml", CreationCollisionOption.ReplaceExisting);
            //Založíme Serializer typu List<User>
            XmlSerializer srl = new XmlSerializer(typeof(List<User>));

            //serializujeme
            srl.Serialize(w, users);

            //zapíšeme přes FileIO
            await FileIO.WriteTextAsync(file, w.ToString());
            // zavřeme StreamWriter
            await w.FlushAsync();

            return true;
        }
        catch
        {
            return false;
        }
    }
}

Hlavní stránka:

public MainPage()
{
    this.InitializeComponent();


}

private async void Uloz_Click(object sender, RoutedEventArgs e)
{
    User users = new User("Pepa",18);
    bool result = await users.Uloz();
}
 
Odpovědět 26.1.2014 15:18
Avatar
Petr Nymsa
Redaktor
Avatar
Odpovídá na KopRus
Petr Nymsa:

Snažíš se serializovat List<User> ale serialuzuješ pouze jeden objekt. Nevím proč v Mainpage máš jednoho Usera ale pojmenuješ to Users. Následně proč ve třídě User máš privátní instanci třídy User. Udělej si nějaký UserManager, který bude mít List<User> a v tomto manageru si vytvoř metodu pro uložení

Odpovědět 26.1.2014 17:15
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
KopRus
Neregistrovaný
Avatar
KopRus:

Už to mám, moc děkuji za rady :)

 
Odpovědět 26.1.2014 18:10
Avatar
jan.janusek
Člen
Avatar
jan.janusek:

Veľmi pekne ti ďakujem za tento tutoriál by the way It work's great on windows phone 8.1 :)

Odpovědět 1.7.2014 19:19
Čo si sám nenakódiš nevieš.
Avatar
cz631642
Člen
Avatar
cz631642:

Zdravím,
serializace nechodí. Když dám breakeoint před:

XmlSerializer srl = new XmlSerializer(ty­peof(List<User>));

Tak se tento řádek neprovede a skočí na false. Zkouším vše co tu je napsáno, ale úspěch nikde.
Dík za radu
Jan

 
Odpovědět 3.6.2015 13:15
Avatar
Jan Vargovský
Redaktor
Avatar
Odpovídá na cz631642
Jan Vargovský:

Můžeš ukázat třídu User?

 
Odpovědět 3.6.2015 13:15
Avatar
cz631642
Člen
Avatar
cz631642:

Jé moc díky, že se někdo ozval. Moje třída není přímo User.
Takže moje konkrétní serializace je:

XmlSerializer srl = new XmlSerializer(zaz­namyPrace.Get­Type());

A trída :

public class PraceZaznam
{
public DateTime Datum { get; private set; }
public string NazevZakazky { get; set; }
public string PopisZakazky { get; set; }

public PraceZaznam()
{

}

public PraceZaznam(Da­teTime datum, string nazevZakazky, string popisZakazky)
{
Datum = datum;
NazevZakazky = nazevZakazky;
PopisZakazky = popisZakazky;
}
public override string ToString()
{
return NazevZakazky;
}

Díky za odpověď.
Jan

 
Odpovědět 3.6.2015 15:53
Avatar
Jan Vargovský
Redaktor
Avatar
Odpovídá na cz631642
Jan Vargovský:

Všechny property musí být veřejně nastavitelné, takže odmaž to private u Datum.

 
Odpovědět 3.6.2015 16:04
Avatar
cz631642
Člen
Avatar
cz631642:

Díky zafungovalo to.
Tohle by mne asi nikdy nenapadlo.
Jan

 
Odpovědět 3.6.2015 17:01
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 17. Zobrazit vše