Beiträge aus Mai 2010

XML Datei mit extrem wenig Codezeilen via Data Binding bearbeiten

Wie kann ich in .NET mit möchst wenigen Codezeilen und möglichst viel XAML eine XML-Datei bearbeiten? Es ist tatsächlich mit sehr wenigen Zeilen möglich. Man sieht nur vor lauter Codebeispielen die Lösung nicht. Im Prinzip sagt die MSDN-Dokumentation schon viel: Mit XMLDataProvider können die einer Oberfläche zugrunde liegenden Daten via Data Binding irgendein XML-Knoten sein.

Hier die XML-Datei, die meiner Beispieloberfläche als Datengrundlage dient:

<?xml version="1.0" encoding="utf-8"?>
<Books xmlns="">
  <Book ISBN="0-7356-0562-9" Stock="in">
    <Title>XML in Action</Title>
    <Summary>XML Web Technology</Summary>
    <Cover>http://www.microsoft.com/library/images/worldwide/mspress/2461.gif</Cover>
  </Book>
  <Book ISBN="0-7356-1377-X" Stock="in">
    <Title>Introducing Microsoft .NET</Title>
    <Summary>Overview of .NET Technology</Summary>
    <Cover>http://www.microsoft.com/library/images/worldwide/mspress/5201.gif</Cover>
  </Book>
  <Book ISBN="0-7356-1288-9" Stock="out">
    <Title>Inside C#</Title>
    <Summary>C# Language Programming</Summary>
    <Cover>http://www.microsoft.com/MSPress/books/imgt/5027.gif</Cover>
  </Book>
  <!--lsdfkgsdlkfgölsdfgls-->
  <Book ISBN="0-7356-1370-2" Stock="in">
    <Title>Programming Microsoft Windows With C#</Title>
    <Summary>C# Programming using the .NET Framework</Summary>
    <Cover>http://www.microsoft.com/MSPress/books/imgt/5188.gif</Cover>
  </Book>
  <Book ISBN="0-7356-1448-2" Stock="out">
    <Title>Microsoft C# Language Specifications</Title>
    <Summary>The C# language definition</Summary>
    <Cover>http://www.microsoft.com/MSPress/books/imgt/5490.gif</Cover>
  </Book>
  <Table>
  </Table>
</Books>

Mit XPath navigiere ich in der Datengrundlade und weise meinen Controls ihren XML-Knoten zu. Hier der XAML-Code:


<Window x:Class="XMLEdit.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Height="350" Width="525">

        <Window.Resources>
        <XmlDataProvider x:Key="BookData" Source="C:\Users\Oliver\Documents\Visual Studio 2010\Projects\XMLEdit\XMLEdit\books.xml" XPath="Books" />
        <DataTemplate x:Key="BookDataTemplate">
            <Border SnapsToDevicePixels="True" Padding="10" Margin="5" CornerRadius="5" BorderThickness="2" BorderBrush="SteelBlue" >
                <StackPanel>
                    <TextBlock Text="{Binding XPath=Title}" FontSize="14" FontWeight="Bold" Margin="0,0,0,5"></TextBlock>
                    <StackPanel Orientation="Horizontal" Margin="5">
                        <Image Source="{Binding XPath=Cover}" Width="100"></Image>
                        <StackPanel Orientation="Vertical">
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Text="ISBN: " FontWeight="Bold"></TextBlock>
                                <TextBox Text="{Binding XPath=@ISBN}" Foreground="OrangeRed"></TextBox>
                            </StackPanel>
                            <TextBox Text="{Binding XPath=Summary}" Margin="0,5,0,0"></TextBox>
                        </StackPanel>
                    </StackPanel>
                </StackPanel>
            </Border>
        </DataTemplate>
        <Style x:Key="StretchedContainerStyle" TargetType="{x:Type ListBoxItem}">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        </Style>

    </Window.Resources>

    <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">

        <StackPanel>
            <TextBlock Text="Edit XML File using Data Binding via XPath and XMLDataProvider" Padding="5" Height="27" VerticalAlignment="Bottom" ></TextBlock>
            <TextBlock Text="This sample uses extremely few code lines" Padding="5" Height="27" VerticalAlignment="Bottom" ></TextBlock>
            <StackPanel>
                <Button Content="Save"
                Click="Button_Save_Click"
                Margin="0 0 0 10" Height="27" Width="56" />
            </StackPanel>
            <Grid x:Name="Grid1">
                <ListBox
                    ItemsSource="{Binding Source={StaticResource BookData}, XPath=Book}"
                    ItemTemplate="{StaticResource BookDataTemplate}"
                    ItemContainerStyle="{StaticResource StretchedContainerStyle}" >
                </ListBox>
            </Grid>
        </StackPanel>
    </ScrollViewer>
</Window>

Ein paar wenige Codezeilen waren dann doch nötig, um das implizit geladene XMLDocument des XMLDataProvider zu speichern:

using System.Windows;
using System.Windows.Data;

namespace XMLEdit
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Save_Click(object sender, RoutedEventArgs e)
        {

            XmlDataProvider myXmlDataProvider = (XmlDataProvider)Grid1.FindResource("BookData");
            myXmlDataProvider.Document.Save("C:\\Users\\Oliver\\Documents\\Visual Studio 2010\\Projects\\XMLEdit\\XMLEdit\\books.xml");
        }
    }
}
 

INTP

In jeden guten Traumjob-Ratgeber heißt es, man solle doch erst mal herausfinden, wer man ist und was einem am leichtesten fällt. Schließlich kann man sich einmal verstellen, aber nicht sein ganzes Berufsleben lang. Und wer weiß, was er will, der kann es wohl unterbewußt auch gut erreichen und ist gut darin. Jetzt habe ich einen Persönlichkeitstest gefunden, bei dem ich mich gut aufgehoben fühle: Der MBTI (Meyers Briggs Type Indicator). Bei dieser Typeneinteilung gibt es 16 Typen. Überraschend an diesem Test finde ich, dass er einem wenig Spielraum für restliche Unsicherheiten läßt. Wer nicht extrovertiert ist, was ist der wohl? Introvertiert natürlich. Und überraschend auch, wie nüchtern das dargestellt wird.
Introvertiert bedeutet nicht etwa ’schüchtern’, sondern einfach nur, dass man seine Energie aus seinem eigenen Inneren bezieht, statt aus seiner Außenwelt. Der Introvertierte kann sich prima konzentrieren, weil er ja gar keine Ablenkung sucht. Andererseits sucht er auch die Stille, um sich zu konzentrieren. Bevor er redet, denkt er nach. Er sucht zuerst in sich selbst nach der Lösung. Dadurch eignet er sich sehr dafür bzw. ist bestrebt, selbständig zu arbeiten.
Als nächstes unterscheidet sich der mit den fünf Sinnen von dem mit dem sechsten Sinn. Während ersterer in der Realität voll aufgeht und sich mit den Gegebenheiten abfindet, lebt letzterer nicht in der Gegenwart, sondern hat Verbesserungsmöglichkeiten im Kopf und lebt im ‘Könnte’. Der Intuitive probiert verschiedene Arbeitsstile aus und läßt seine Erfahrung hinter sich. Er liebt die Herausforderung am Lernen neuer Fähigkeiten und folgt seiner Inspiration statt Fakten. Er interessiert sich nicht für Details, sondern für generelle Prinzipien. Ich habe eine interessante Diskussion gelesen, wie denn so ein Intuitiver als Programmierer arbeiten könnte, wo es doch bei der Programmierung extrem ins Detail geht. Schließlich sind unter Programmierern und Softwareentwicklern auffällig viele INTP vorhanden, während der Typ INTP in der Allgemeinbevölkerung sehr selten ist. Nun, beim Programmieren geht es immer noch ums abstrahieren von den wirklich langweiligen Details, die man eben den Computer machen lassen möchte. Und es ist allgemeiner Konsens, dass man jedes Detail nur einmal schreiben und ansonsten wieder verwenden sollte.
Als Drittes wird der fühlende vom denkenden Menschen unterschieden. Hier geht es nicht um Emotionen, unter denen der eine wie der andere Typ leiden können. Gefühl meint, dass man mit anderen mitfühlt, die Wahrheit um des Taktgefühls willen verschweigt und sich für Harmonie interessiert. Der Denker ist ein aufgabenorientierter Mensch. Er will seine Arbeit erledigen und stört sich wenig an Gefühlen. Bereitwillig äußert er Kritik, um sein Ziel besser erreichen zu können.
Schließlich gibt es den Planenden vs. Flexiblen. Der Planende arbeitet am Besten, wenn alles nach Plan verläuft, während der Flexible bei Bedarf handelt. Er schiebt Entscheidungen auf, um mehr Möglichkeiten zu haben. Er widersteht Strukturen und favorisiert sich ändernde Umstände. Der Planende hingegen liebt Termine und erledigt lieber seine Aufgabe pünktlich. Dabei übersieht er neue Möglichkeiten, die am Wegrand stehen.
Nur beim letzten Punkt bin ich mir nicht sicher, ob ich eher ein Planender oder ein Flexibler bin. Eigentlich bin ich extrem aufgabenorientiert, aber wenn es um meinen Urlaub geht, störe ich mich an jedem Plan und möchte unbedingt in den Tag hinein leben.

Angenommen ich bin nun INTP, kann ich mich in die Schublade von Albert Einstein stecken.

 

Erbsenzähler

Zur Übung, um C# und .NET zu lernen, schreibe ich mir einen Erbsen… äh Zeilenzähler. In XAML schreibe ich mir mal eben eine Oberfläche mit einem Button ‘Load File’, auf dessen Betätigung hin eine Visual Studio C++-Projektdatei auf ihre Filter (Ordner) hin untersucht wird und eine Auswahl an diesen Filtern angeboten wird. Mit dem Button ‘Count’ werden dann alle Zeilen der in diesen virtuellen Ordnern befindlichen Dateien gezählt.

<Window x:Class="TestFileDialog2343.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="350">

    <StackPanel Margin="10" HorizontalAlignment="Left">
        <StackPanel Name="ChooseFilters" HorizontalAlignment="Left" Height="28" Width="91">
            <Button Content="Load File"
                Click="Button_LoadFile_Click"
                Margin="0 0 0 10"/>
        </StackPanel>
        <ListBox ItemsSource="{Binding FilterSelections}" IsSynchronizedWithCurrentItem="True"
        Height="87" HorizontalAlignment="Left" Margin="12,69,0,0" Name="listBox1" VerticalAlignment="Top" Width="276" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <CheckBox IsChecked="{Binding Check}" />
                        <TextBlock Text="{Binding Name}" />

                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <StackPanel>
            <Button Content="Count"
                Click="Button_Count_Click"
                Margin="0 0 0 10"/>
        </StackPanel>
    </StackPanel>
</Window>

Was ich hier außer handwerklichen Dingen wie mit XML-Dokumenten umgehen oder das eigentliche Zeilen zählen gelernt habe, ist die Datenbindung. Hier der Quellcode:

using System;
using System.Windows;
using Microsoft.Win32;
using System.IO;
using System.Xml;
using System.Collections.ObjectModel;

namespace TestFileDialog2343
{
    public class FilterSelection
    {
        public string Name { get; set; }
        public bool Check { get; set; }
    }

    public partial class Window1 : Window
    {
        public ObservableCollection<FilterSelection> FilterSelections { get; internal set; }
        public XmlNodeList FiltersNode;

        public Window1()
        {
            FilterSelections = new ObservableCollection<FilterSelection>();
            InitializeComponent();

            this.DataContext = this;
        }

        private void Button_LoadFile_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.DefaultExt = ".vcproj";
            dlg.InitialDirectory = @"C:\Quellen\nova\Sources\XElektro";
            dlg.Filter = "Projektdateien (.vcproj)|*.vcproj";

            bool? result = dlg.ShowDialog();

            if (result == true)
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(dlg.FileName);

                XmlElement oNode = doc.DocumentElement;

                //XmlNodeList oNodeList = oNode.SelectNodes("/VisualStudioProject/Files/Filter");
                FiltersNode = oNode.SelectNodes("//Filter");

                foreach (XmlNode i in FiltersNode)
                {
                    FilterSelection fs = new FilterSelection();
                    FilterSelections.Add(fs);
                    fs.Name = i.Attributes["Name"].Value;
                }

            }
        }

        private XmlNode GetNode(string name)
        {
            foreach (XmlNode i in FiltersNode)
                if (name == i.Attributes["Name"].Value)
                    return i;

            return null;
        }

        public void Button_Count_Click(object sender, RoutedEventArgs e)
        {
            int count = 0;
            foreach (FilterSelection fs in FilterSelections)
            {
                if (!fs.Check)
                    continue;

                XmlNode i = GetNode(fs.Name);

                System.Diagnostics.Debug.WriteLine(fs.Name);

                if (i == null)
                    continue;

                int r = CountLines(i);

                System.Diagnostics.Debug.WriteLine(r);
                count += r;
            }
        }

        private string GetFilename(XmlNode node)
        {
            Uri uri = new Uri(node.BaseURI);
            string dir = uri.AbsolutePath;
            int lastslash = dir.LastIndexOf("/");
            dir = dir.Remove(lastslash + 1);

            string file = node.Attributes["RelativePath"].Value;
            lastslash = file.LastIndexOf("\\");
            file = file.Substring(lastslash + 1);

            return dir + file;
        }

        private int CountLines(XmlNode filenode)
        {
            int count = 0;

            XmlNodeList list = filenode.SelectNodes("File");
            foreach (XmlNode fnode in list)
            {
                string filename = GetFilename(fnode);

                if (!File.Exists(filename))
                    continue;

                System.Diagnostics.Debug.WriteLine(filename);
                using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
                {
                    using (StreamReader sr = new StreamReader(fs))
                    {
                        while (!sr.EndOfStream)
                        {
                            string line = sr.ReadLine();
                            if (line.Length > 0)
                                count++;
                        }
                    }
                }
            }
            return count;
        }
    }
}

Das schöne an WPF ist die mögliche Trennung zwischen Daten und Ansicht. Ich binde meine Listbox an eine ObservableCollection von Objekten der Klasse FilterSelection und den Inhalt der Klasse binde ich an eine benamte Checkbox. So kann ich für jeden Filter entscheiden, ob er mitgezählt werden soll. Unkomplizierter Weise fülle ich eben nicht den Dialog, sondern die Collection mit Daten und die Oberfläche ändert sich wie von Geisterhand. Anders herum ändern sich auch die Daten automatisch mit der Änderung des Benutzers der Obefläche. So brauche ich auch nur die Daten auszulesen und herauszufinden, welche Filter der Benutzer gezählt bekommen möchte.