AdSense

Posts mit dem Label WPF werden angezeigt. Alle Posts anzeigen
Posts mit dem Label WPF werden angezeigt. Alle Posts anzeigen

Dienstag, 17. September 2013

Musikplayer - Handy-Fernsteuerung

(English version) Wie in diesem Post beschrieben habe ich einen eigenen Musikplayer mit C# und WPF geschrieben. Da es bei größeren Wiedergabelisten durchaus vorkommen kann, dass einem mal ein Lied nicht gefällt, gibt es Hotkeys für den PC. Was ist aber, wenn man gerade gemütlich auf dem Bett liegt und man keine Lust hat, aufzustehen? Dafür habe ich eine App geschrieben, mit welcher man die grundlegenden Hotkeys (Play/Pause, nächstes Lied, vorheriges Lied) ausführen kann. Diese App kann man hier herunterladen.

Zur Verwendung der App: Die App kann man einerseits starten und dann auf die entsprechenden Buttons klicken, es gibt jedoch auch ein Widget, welches den Klick zum App Öffnen spart. Die App bietet jedoch mehr Funktionen, sogar das suchen nach einem Lied ist möglich.

In der App muss man nun IP, Port (Der Player macht standardmäßig einen Socket auf Port 7200 auf) und Passwort (welches man im Player mit F2 festlegen kann) eingeben. Dabei kann die IP lokal sein (z.B. wenn das Handy im selben Netzwerk ist wie das Handy) oder auch eine Internet-IP. Dafür muss man jedoch am Router noch eine entsprechende Port-Weiterleitung einrichten. Das Handy kann den Player in diesem Fall unabhängig davon steuern, ob es im WLAN oder im mobilen Netzwerk ist.

Montag, 2. September 2013

C# WPF - Invoken

(English version) Im vorigen Post taucht das Problem auf: Ich darf auf bestimmte Grafik-Sachen nur aus dem Haupt-Thread zugreifen. Dummerweise kann es jedoch immer vorkommen, dass man auch aus einem anderen Thread etwas tun will. Die Lösung dafür heißt: Invoken. Wie das geht zeige ich nun. Zuerst ein Beispiel, wie etwas nicht funktioniert:

void threadActivity()
{
    Thread.Sleep(5000);
    TaskbarItemInfo.ProgressValue = 0.2;
}


Sobald 5 Sekunden um sind wird ein Fehler auftreten: InvalidOperationException wurde nicht behandelt. Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da sich das Objekt im Besitz eines anderen Threads befindet. Wie schon angekündigt heißt die Lösung hierfür: Invoken. Der veränderte Code sieht nun folgendermaßen aus:

void threadActivity()
{
    Thread.Sleep(5000);
    Application.Current.Dispatcher.BeginInvoke(new Action(() =>
    {
        TaskbarItemInfo.ProgressValue = 0.2;
    }));
}


Sobald nun 5 Sekunden um sind wird sich der Fortschrittsbalken in der Taskleiste auf den Wert 0.2 einstellen. Zur Syntax: alles in den inneren geschweiften Klammern wird im Haupt-Thread ausgeführt, innerhalb der Klammern kann man auf Variablen, auf welche man außerhalb der geschweiften Klammern zugreifen kann, auch zugreifen. Auch darf ruhig deutlich mehr als nur ein Befehl darin stehen (wobei es ratsamer ist, nur möglichst kurze Code-Bausteine zu invoken).

C# WPF - Fortschrittsbalken in der Taskleiste anzeigen

(English version) Bei Downloads mit dem Firefox unter Windows 7 wird in der Taskleiste ein Fortschrittsbalken angezeigt. Das selber zu realisieren ist total einfach. In dem entsprechenden Fenster muss

<Window.TaskbarItemInfo>
    <TaskbarItemInfo/>
</Window.TaskbarItemInfo>


eingefügt werden, direkt vor dem standartmäßigen <Grid>. Damit kann man nun auf TaskbarItemInfo zugreifen. Das sieht dann z.B. folgendermaßen aus:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();


        TaskbarItemInfo.ProgressState = System.Windows.Shell.TaskbarItemProgressState.Normal;
 
        Loaded += MainWindow_Loaded;
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        TaskbarItemInfo.ProgressValue = 0.5;

    }
}


Man kann nun auch verschiedene ProgressStates benutzen, dementsprechend verändert der Fortschrittsbalken seine Farbe. Ebenfalls kann man über TaskbarItemInfo auch ein Symbol in der Taskleiste festlegen.

Will man all dies jedoch aus einem anderen Thread tun, so wird es nicht funktionieren. Warum das so ist erkläre ich in diesem Post. Hier nur schnell die Lösung dafür. Statt

TaskbarItemInfo.ProgressValue = 0.2

benutzt man einfach

Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
    TaskbarItemInfo.ProgressValue = 0.2;
}));

Mittwoch, 28. August 2013

C# WPF - DataBinding

(English version) DataBindings in WPF sind ziemlich mächtig und nützlich. Heute will ich hier die Grundlagen beschreiben. Die Grundidee dahinter ist: Ich habe ein Anzeigeelement und eine Variable (eigentlich ein Feld). Wenn ich die Variable verändere, will ich, dass sich das Anzeigeelement ändert, umgekehrt will ich aber auch, dass sich bei Veränderung des Anzeigeelements (z.B. Texteingabe) die Variable ändert. Hier nun mal ein kleines Beispiel. Zuerst die XAML-Seite:

<TextBox Name="InputText" Grid.Row="0" Text="{Binding BindingText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
<TextBlock Name="OutputText" Grid.Row="1" Text="{Binding BindingText, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"></TextBlock>


Als nächstes braucht man irgendein Objekt, an welches gebindet wird. Das ist bei mir die Klasse "AnyClass", welche das Feld "BindingText" enthält, man könnte jedoch auch genausogut einfach MainWindow.xaml.cs benutzen. Um jetzt das Objekt an welches gebindet wird festzulegen geht man folgendermaßen vor:

AnyClass anyClass = new AnyClass();
InputText.DataContext = anyClass;
OutputText.DataContext = anyClass;


Man könnte auch einfach this.DataContext = anyClass; machen, was prinzipiell auch richtig ist, ich will hier aber möglichst viele Möglichkeiten zeigen. Ebenfalls könnte ich einfach in MainWindow.xaml.cs das entsprechende Feld "BindingText" erstellen und dann this.DataContext = this; aufrufen.

Nun komme ich zur Klasse AnyClass. Zuerst eine kurze Einführung zum Thema Felder. Ein Feld kann wie eine Variable verwendet werden, jedoch kann ein Feld keine Werte direkt speichern. Ein minimales Feld sieht folgendermaßen aus:

public string BindingText
{
    get { return _BindingText; }
    set { _BindingText = value; }
}
private string _BindingText = "";


Das ist also ein Feld. Im get und set Block kann man nun viele lustige Dinge tun, wie z.B. Überprüfen von Eingaben, Events werfen (also ein event, wenn das Feld abgefragt wird z.B.) und eigentlich auch alles andere denkbare.

Nun zeige ich die Klasse AnyClass, welche der letzte fehlende Baustein für das DataBinding ist:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DataBindingExample
{
    public class AnyClass : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string BindingText
        {
            get { return _BindingText; }
            set
            {
                _BindingText = value;
                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs("BindingText"));
                }
            }
        }
        private string _BindingText = "<empty>";
    }
}


Indem man PropertyChanged auslöst bekommt das Anzeigeelement gesagt, dass ein neuer Variablenwert existiert und aktualisiert sich. Das sollte die Grundlagen zum Thema DataBinding abdecken, falls es Fragen gibt, beantworte ich diese gerne in den Kommentaren.

Einen Post zu diesem Thema, der auch etwas weiter greift und z.B. Converter erklärt gibt es auf C# Tipps und Tricks.

Freitag, 23. August 2013

Ein Musikplayer mit C#

(English version) Tja, es gibt viele Musikplayer auf der Welt. Jeder hat Vorteile und Nachteile, bisher habe ich keinen "perfekten" Musikplayer gefunden. Daher habe ich mich mal drangesetzt und einen eigenen Programmiert. Den Player kann man hier herunterladen. Zur Bedienung: Man muss einmal einen Ordner als Musikbibliothek auswählen und mit Refresh Library wird diese dann geladen. Play Pause, etc sollte selbsterklärend sein. Was mir ganz wichtig ist sind die Hotkeys: "AltGr" + "NachRechts" spielt das nächste (zufällige) Lied, mit "AltGr" + "NachLinks" das vorherige. Außerdem kann die Wiedergabe mit "AltGr" + "Leertaste" angehalten und fortgesetzt / gestartet werden. Unter dem Fortschrittsbalken des aktuellen Liedes gibt es ein Such-Fenster, hier kann man Suchbegriffe eintippen. Ein Doppelklick auf ein Lied startet dieses dann. Zwei kleine Programmierkonzepte werde ich hier nun vorstellen: Einerseits das Abspielen von Medien-Dateien mit C# und WPF, andererseits das Verwenden von Hotkeys. Übrigens kann man hier eine grundlegende Einführung in WPF finden: C# Tipps und Tricks.

Musik wiedergeben

Da ich WPF verwende ist das total simpel: In der xaml-Datei habe ich einfach als Element (so wie z.B. ein Button) ein MediaElement:

<MediaElement Height="10" Width ="10"  LoadedBehavior="Manual" Name="mediaElement1" VerticalAlignment="Top" />

Das MediaElement ist unsichtbar. Um jetzt Musik abzuspielen geht man folgendermaßen vor:

mediaElement1.Source = new Uri(pathToMediaFile);
mediaElement1.Play();


Das war auch schon alles was man dazu sagen muss. Man kann sich jetzt noch an Events hängen, z.B. an mediaElement1_MediaEnded, sodass man weiß, wann die Wiedergabe aufgehört hat (um z.B. das nächste Lied zu starten).

Hotkeys

Anmerkung: Inzwischen benutze ich eine "bessere" Implementierung der Hotkeys, welche man hier nachlesen kann.

Hotkeys zu verwenden ist recht einfach. Zuerst muss man ein paar Zeilen in das Programm einfügen:

[DllImport("User32.dll")]
private static extern bool RegisterHotKey(
    [In] IntPtr hWnd,
    [In] int id,
    [In] uint fsModifiers,
    [In] uint vk);

[DllImport("User32.dll")]
private static extern bool UnregisterHotKey(
    [In] IntPtr hWnd,
    [In] int id);

private HwndSource _source;
private const int HOTKEY_ID = 9000;

protected override void OnSourceInitialized(EventArgs e)
{
    base.OnSourceInitialized(e);
    var helper = new WindowInteropHelper(this);
    _source = HwndSource.FromHwnd(helper.Handle);
    _source.AddHook(HwndHook);
    RegisterHotKey();
}

protected override void OnClosed(EventArgs e)
{
    _source.RemoveHook(HwndHook);
    _source = null;
    UnregisterHotKey();
    base.OnClosed(e);
}

private void UnregisterHotKey()
{
    var helper = new WindowInteropHelper(this);
    UnregisterHotKey(helper.Handle, HOTKEY_ID);
}

private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    const int WM_HOTKEY = 0x0312;
    switch (msg)
    {
        case WM_HOTKEY:
            OnHotKeyPressed(wParam.ToInt32());
            break;
    }
    return IntPtr.Zero;
}


Als nächstes habe ich die drei Hotkeys, welche ich benötige definiert:

private void RegisterHotKey()
{
    var helper = new WindowInteropHelper(this);
    const uint VK_RIGHT = 0x27;
    const uint VK_LEFT = 0x25;
    const uint VK_SPACE = 0x20;
    const uint MOD_ALT = 0x0001;
    const uint MOD_CTRL = 0x0002;

    if (!RegisterHotKey(helper.Handle, HOTKEY_ID+2, MOD_CTRL + MOD_ALT, VK_RIGHT))
    {
        // handle error
    }
    if (!RegisterHotKey(helper.Handle, HOTKEY_ID+1, MOD_CTRL + MOD_ALT, VK_LEFT))
    {
        // handle error
    }
    if (!RegisterHotKey(helper.Handle, HOTKEY_ID, MOD_CTRL + MOD_ALT, VK_SPACE))
    {
        // handle error
    }
}


Nun braucht es nur noch die Funktion, welche die gewünschten Aktionen ausführt, wenn man einen Hotkey drückt:

private void OnHotKeyPressed(int key)//9000 = space, 9001 = left, 9002 = right
{
    //System.Console.Write("KeyPressed! {0}\n", key);
    switch (key)
    {
        case 9000: if (playing) { pause(); } else { play(); }
            break;
        case 9001: back();
            break;
        case 9002: next();
            break;
        default:
            break;
    }
    // do stuff
}


Was genau hier passiert ist auch relativ simpel zu erkennen und sollte selbsterklärend sein. Für die Hotkeys muss man nun weiter nichts mehr tun. Zu beachten ist jedoch, dass diese Hotkeys nur bei meinem Programm funktionieren, solange es offen ist. Würde ich also z.B. "AltGr" + "0" als Hotkey nehmen, so könnte ich kein } mehr schreiben, während das Programm läuft.

Donnerstag, 25. Juli 2013

C# WPF - 3D-Grafik

(English version) Bisher hatte ich es nicht geschafft, 3D-Grafiken zu erzeugen (DirectX, und andere habe ich ausprobiert, von denen hat jedoch nichts schön funktioniert). Heute bin ich per Zufall darauf gestoßen, dass WPF das auch kann. Also hab ich mich hingesetzt und in 2 Stunden war ein kleiner Panzer, den man mit Pfeiltasten fahren kann fertig. Mit WPF 3D-Grafik zu machen ist höchstgradig trivial.

Wie man im Code sehen kann besteht der Hauptteil des Programms aus der Funktion Tank(), welche daraus besteht, dass ich alle Objekte von Hand baue. Das geht prinzipiell auch viel einfacher mit 3D-Editoren oder aus XML-Dateien, für den ersten Test werde ich es aber mal so belassen.

Ansonsten gibt es da noch den moveThread, welcher den Panzer bewegt und dann noch die Funktion RefreshEverything, welche die Position des Panzers verändert. Falls es Fragen zu irgendwas gibt stehe ich gerne Rede und Antwort.
 
Hier nun mein Code von MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace Wpf3dTest1
{
    public partial class MainWindow : Window
    {
        Dictionary<Key, Boolean> pressedKeys = new Dictionary<Key, Boolean>();
        bool quit = false;
        public MainWindow()
        {
            InitializeComponent();

            pressedKeys.Add(Key.Left, false);
            pressedKeys.Add(Key.Right, false);
            pressedKeys.Add(Key.Up, false);
            pressedKeys.Add(Key.Down, false);

            this.KeyDown += MainWindow_KeyDown;
            this.KeyUp += MainWindow_KeyUp;

            this.Closing += OnWindowClosing;

            //create a Tank object and add to viewport
            mod = Tank(new Point3D(xPos, 0, 0), 2.2, 1.8, 0.7, 0.3, 0.25, 0.5, 0.25, new DiffuseMaterial(Brushes.Green), new DiffuseMaterial(Brushes.Black));
            viewport3D.Children.Add(mod);

            //start a Thread which moves the object
            Thread thread = new Thread(moveThread);
            thread.Start();
        }

        void MainWindow_KeyUp(object sender, KeyEventArgs e)
        {
            if (pressedKeys.ContainsKey(e.Key))
            {
                pressedKeys[e.Key] = false;
            }
        }

        void MainWindow_KeyDown(object sender, KeyEventArgs e)
        {
            if (pressedKeys.ContainsKey(e.Key))
            {
                pressedKeys[e.Key] = true;
            }
        }

        ModelVisual3D mod;

        private delegate void RefreshEverythingDelegate();
        private void RefreshEverything()
        {
            TranslateTransform3D translate = new TranslateTransform3D();
            translate.OffsetX = xPos;
            translate.OffsetZ = zPos;

            RotateTransform3D rotate = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1,0),angle));
            rotate.CenterX = xPos;
            rotate.CenterZ = zPos;


            Transform3DGroup transform = new Transform3DGroup();
            transform.Children.Add(translate);
            transform.Children.Add(rotate);
            mod.Transform = transform;
        }

        //Parameters for turn and move
        private double posIncrementor = 0.02;
        private double xPos = 0;
        private double zPos = 0;

        private double angle = 0;
        private double angleIncrementor = 0.4;

        private void moveThread()
        {
            while (!quit)
            {
                if (pressedKeys[Key.Up])
                {
                    xPos += posIncrementor * Math.Cos(angle * Math.PI/180) ;
                    zPos -= posIncrementor * Math.Sin(angle * Math.PI / 180);
                }
                if (pressedKeys[Key.Down])
                {
                    xPos -= posIncrementor * Math.Cos(angle * Math.PI / 180);
                    zPos += posIncrementor * Math.Sin(angle * Math.PI / 180);
                }
                if (pressedKeys[Key.Left])
                {
                    angle += angleIncrementor;
                }
                if (pressedKeys[Key.Right])
                {
                    angle -= angleIncrementor;
                }
                DispatcherOperation dispOp = this.viewport3D.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new RefreshEverythingDelegate(RefreshEverything));
                Thread.Sleep(10);
            }
        }

        public void OnWindowClosing(object sender, CancelEventArgs e)
        {
            quit = true;
        }

        private ModelVisual3D Tank(Point3D position, double length, double width, double height, double chainWidth, double chainHeight1, double chainHeight2, double chainNotLength, Material bodyMaterial, Material chainMaterial)
        {
            //Tank Body
            MeshGeometry3D bodyMesh = new MeshGeometry3D();
            Point3D a = new Point3D(position.X - length / 2, 0, position.Z + width / 2);
            Point3D b = new Point3D(position.X + length / 2, 0, position.Z + width / 2);
            Point3D c = new Point3D(position.X + length / 2, 0, position.Z - width / 2);
            Point3D d = new Point3D(position.X - length / 2, 0, position.Z - width / 2);
            Point3D e = new Point3D(position.X - length / 2, position.Y + height, position.Z + width / 2);
            Point3D f = new Point3D(position.X + length / 2, position.Y + height, position.Z + width / 2);
            Point3D g = new Point3D(position.X + length / 2, position.Y + height, position.Z - width / 2);
            Point3D h = new Point3D(position.X - length / 2, position.Y + height, position.Z - width / 2);
            BuildRectangle(bodyMesh, a, b, f, e, new Vector3D(0, 0, 1));
            BuildRectangle(bodyMesh, b, c, g, f, new Vector3D(1, 0, 0));
            BuildRectangle(bodyMesh, c, d, h, g, new Vector3D(0, 0, -1));
            BuildRectangle(bodyMesh, d, a, e, h, new Vector3D(-1, 0, 0));
            BuildRectangle(bodyMesh, e, f, g, h, new Vector3D(0, 1, 0));
            BuildRectangle(bodyMesh, a, d, c, b, new Vector3D(0, -1, 0));

            //Build the model object
            GeometryModel3D BodyModel = new GeometryModel3D(
            bodyMesh,
            bodyMaterial);


            //Chain 1
            MeshGeometry3D chain1Mesh = new MeshGeometry3D();//links, also -width
            a = new Point3D(position.X - length / 2 + chainNotLength, 0, position.Z - width / 2 - chainWidth);
            b = new Point3D(position.X - length / 2 + chainNotLength, 0, position.Z - width / 2);
            c = new Point3D(position.X + length / 2 - chainNotLength, 0, position.Z - width / 2 - chainWidth);
            d = new Point3D(position.X + length / 2 - chainNotLength, 0, position.Z - width / 2);
            e = new Point3D(position.X - length / 2, position.Y + chainHeight1, position.Z - width / 2 - chainWidth);
            f = new Point3D(position.X - length / 2, position.Y + chainHeight1, position.Z - width / 2);
            g = new Point3D(position.X + length / 2, position.Y + chainHeight1, position.Z - width / 2 - chainWidth);
            h = new Point3D(position.X + length / 2, position.Y + chainHeight1, position.Z - width / 2);
            Point3D i = new Point3D(position.X - length / 2, position.Y + chainHeight2, position.Z - width / 2 - chainWidth);
            Point3D j = new Point3D(position.X - length / 2, position.Y + chainHeight2, position.Z - width / 2);
            Point3D k = new Point3D(position.X + length / 2, position.Y + chainHeight2, position.Z - width / 2 - chainWidth);
            Point3D l = new Point3D(position.X + length / 2, position.Y + chainHeight2, position.Z - width / 2);

            BuildRectangle(chain1Mesh, a, b, d, c, new Vector3D(0, -1, 0));
            BuildRectangle(chain1Mesh, a, e, f, b, new Vector3D(-1, -1, 0));
            BuildRectangle(chain1Mesh, c, d, h, g, new Vector3D(1, -1, 0));
            BuildRectangle(chain1Mesh, a,c,g,e, new Vector3D(0, 0, -1));
            BuildRectangle(chain1Mesh, i, j, f, e, new Vector3D(-1, 0, 0));
            BuildRectangle(chain1Mesh, k, g, h, l, new Vector3D(1, 0, 0));
            BuildRectangle(chain1Mesh, e,g,k,i, new Vector3D(0, 0, -1));
            BuildRectangle(chain1Mesh, i, k, l, j, new Vector3D(0, 1, 0));

            //Build the model object
            GeometryModel3D Chain1Model = new GeometryModel3D(
            chain1Mesh,
            chainMaterial);


            //Chain 2
            MeshGeometry3D chain2Mesh = new MeshGeometry3D();//rechts, also +width
            a = new Point3D(position.X - length / 2 + chainNotLength, 0, position.Z + width / 2);
            b = new Point3D(position.X - length / 2 + chainNotLength, 0, position.Z + width / 2 + chainWidth);
            c = new Point3D(position.X + length / 2 - chainNotLength, 0, position.Z + width / 2);
            d = new Point3D(position.X + length / 2 - chainNotLength, 0, position.Z + width / 2 + chainWidth);
            e = new Point3D(position.X - length / 2, position.Y + chainHeight1, position.Z + width / 2);
            f = new Point3D(position.X - length / 2, position.Y + chainHeight1, position.Z + width / 2 + chainWidth);
            g = new Point3D(position.X + length / 2, position.Y + chainHeight1, position.Z + width / 2);
            h = new Point3D(position.X + length / 2, position.Y + chainHeight1, position.Z + width / 2 + chainWidth);
            i = new Point3D(position.X - length / 2, position.Y + chainHeight2, position.Z + width / 2);
            j = new Point3D(position.X - length / 2, position.Y + chainHeight2, position.Z + width / 2 + chainWidth);
            k = new Point3D(position.X + length / 2, position.Y + chainHeight2, position.Z + width / 2);
            l = new Point3D(position.X + length / 2, position.Y + chainHeight2, position.Z + width / 2 + chainWidth);

            BuildRectangle(chain2Mesh, a, b, d, c, new Vector3D(0, -1, 0));
            BuildRectangle(chain2Mesh, a, e, f, b, new Vector3D(-1, -1, 0));
            BuildRectangle(chain2Mesh, c, d, h, g, new Vector3D(1, -1, 0));
            BuildRectangle(chain2Mesh, b, f, h, d, new Vector3D(0, 0, 1));
            BuildRectangle(chain2Mesh, i, j, f, e, new Vector3D(-1, 0, 0));
            BuildRectangle(chain2Mesh, k, g, h, l, new Vector3D(1, 0, 0));
            BuildRectangle(chain2Mesh, f, j, l, h, new Vector3D(0, 0, 1));
            BuildRectangle(chain2Mesh, i, k, l, j, new Vector3D(0, 1, 0));

            //Build the model object
            GeometryModel3D Chain2Model = new GeometryModel3D(
            chain2Mesh,
            chainMaterial);


            //Build the whole object
            Model3DGroup model3DGroup = new Model3DGroup();
            model3DGroup.Children.Add(BodyModel);
            model3DGroup.Children.Add(Chain1Model);
            model3DGroup.Children.Add(Chain2Model);

            ModelVisual3D model = new ModelVisual3D();
            model.Content = model3DGroup;
            return model;
        }

        //Funktioniert auch mit nicht-Rechtecken, also wenn nicht alle Innenwinkel 90° sind.
        private void BuildRectangle(MeshGeometry3D geometry,Point3D a, Point3D b, Point3D c, Point3D d,Vector3D normal)
        {
            int baseIndex = geometry.Positions.Count;

            //Add vertices
            geometry.Positions.Add(a);
            geometry.Positions.Add(b);
            geometry.Positions.Add(c);
            geometry.Positions.Add(d);

            //Add normals
            geometry.Normals.Add(normal);
            geometry.Normals.Add(normal);
            geometry.Normals.Add(normal);
            geometry.Normals.Add(normal);

            //Add indices
            geometry.TriangleIndices.Add(baseIndex + 0);
            geometry.TriangleIndices.Add(baseIndex + 2);
            geometry.TriangleIndices.Add(baseIndex + 1);
            geometry.TriangleIndices.Add(baseIndex + 2);
            geometry.TriangleIndices.Add(baseIndex + 0);
            geometry.TriangleIndices.Add(baseIndex + 3);
        }
    }
}


Und hier noch der entsprechende Code für MainWindow.xaml:

<Window x:Class="Wpf3dTest1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Viewport3D Name="viewport3D">
            <Viewport3D.Camera>
                <PerspectiveCamera Position="20,10,10"
                                   LookDirection="-20,-10,-10"
                                   UpDirection="0,1,0"
                                   FieldOfView="45"
                                   NearPlaneDistance="1"
                                   FarPlaneDistance="100"></PerspectiveCamera>
            </Viewport3D.Camera>
            <ModelUIElement3D>
                <AmbientLight Color="White"></AmbientLight>
            </ModelUIElement3D>
        </Viewport3D>
    </Grid>
</Window>

Mittwoch, 17. Juli 2013

C# Windows Forms bzw. WPF - Fensterposition und Größe beibehalten

(English version) Es kann ziemlich praktisch sein, wenn bei einem Neustart der Anwendung jedes Fenster wieder seine Größe und Position hat. Dafür habe ich in C# eine eigene Klasse geschrieben: SizeSavedWindow. Der Quellcode für diese Klasse ist ganz unten im Post. Um nun ein Fenster genau an seiner Position zu halten muss hinter InitializeComponent(); einfach die folgende Zeile eingefügt werden:

SizeSavedWindow.addToSizeSavedWindows(this);

Diese Funktion kann auch für viele verschiedene Fenster aufgerufen werden, die Fenster werden per Name in die xml-Datei gespeichert, wenn es also keine Fenster mit gleichem Namen gibt klappt alles wie es soll. Diese Klasse gibt es auch für WPF Windows, dafür muss jedes "Form" durch "Window" ersetzt werden und auch die Events müssen etwas überarbeitet werden (window_IsVisibleChanged statt window_Shown). Falls jemand Interesse an dem Code für WPF hat kann ich den auch noch posten. Im Anschluss nun den Code für die Klasse SizeSavedWindow (der Namespace muss natürlich noch an das jeweilige Projekt angepasst werden):

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Runtime.Serialization;
using System.Xml;
using System.Windows.Forms;

namespace KeepSizeTest1
{
    public class SizeSavedWindow
    {
        // To keep a window at the same size and position, just add
        // SizeSavedWindow.addToSizeSavedWindows(this);
        // right after initialieComponent
        public static void addToSizeSavedWindows(Form window)
        {
            window.Shown += window_Shown;
        }

        static void window_Shown(object sender, EventArgs e)
        {
            Form window = (Form)sender;
            if (!window.Visible)
            {
                return;
            }
            if (File.Exists("sizes.xml"))
            {
                var stream = new FileStream("sizes.xml", FileMode.Open);
                var reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());
                var deserializer = new DataContractSerializer(typeof(Dictionary<string, int[]>));
                windows = (Dictionary<string, int[]>)deserializer.ReadObject(reader, true);
                stream.Close();

                foreach (KeyValuePair<string, int[]> pair in windows)
                {
                    if (pair.Key == window.Name)
                    {
                        window.Height = pair.Value[0];
                        window.Width = pair.Value[1];
                        window.Top = pair.Value[2];
                        window.Left = pair.Value[3];
                        break;
                    }
                }
            }
            int[] sizes = new int[4];
            sizes[0] = window.Height;
            sizes[1] = window.Width;
            sizes[2] = window.Top;
            sizes[3] = window.Left;
            if (windows.ContainsKey(window.Name))
            {
                windows.Remove(window.Name);
            }
            windows.Add(window.Name, sizes);
            window.SizeChanged += window_SizeChanged;
            window.LocationChanged += window_LocationChanged;
        }

        static void window_SizeChanged(object sender, EventArgs e)
        {
            Form realSender = (Form)sender;
            if (windows.ContainsKey(realSender.Name))
            {
                windows[realSender.Name][0] = realSender.Height;
                windows[realSender.Name][1] = realSender.Width;

                var writer = new FileStream("sizes.xml", FileMode.Create);
                Type type = windows.GetType();
                var serializer = new DataContractSerializer(type);
                serializer.WriteObject(writer, windows);
                writer.Close();
            }
        }

        static void window_LocationChanged(object sender, EventArgs e)
        {
            Form realSender = (Form)sender;
            if (windows.ContainsKey(realSender.Name))
            {
                windows[realSender.Name][2] = realSender.Top;
                windows[realSender.Name][3] = realSender.Left;

                var writer = new FileStream("sizes.xml", FileMode.Create);
                Type type = windows.GetType();
                var serializer = new DataContractSerializer(type);
                serializer.WriteObject(writer, windows);
                writer.Close();
            }
        }

        static Dictionary<string, int[]> windows = new Dictionary<string, int[]>();
    }
}