C# « Bernhard Krenz

Tag: C#

File.Move nach File.Delete

Die Windows-Welt hält doch immer wieder Überraschungen bereit. Gerade brachten mich folgende Code-Zeilen zur Verzweiflung:

if (File.Exists(filePath + ".backup"))
{
	File.Delete(filePath + ".backup");
}

if (File.Exists(filePath))
{
	File.Move(filePath, filePath + ".backup");
}

Ziel des Ganzen: Eine Datei sollte vor dem Überschreiben in eine backup-Datei gespeichert werden. Existierte diese backup-Datei bereits, wird sie vorher gelöscht.

Der Code erzeugte eine IOException mit der Meldung, dass die Datei nicht bewegt werden konnte, da diese bereits existiert. Mit dem oberen Code natürlich völlig unmöglich. Eine Recherche zeigte als Ursache, dass das File.Delete möglicherweise zu lange dauert und zum Zeitpunkt des File.Move noch existierte. Für mich ist dann fraglich, warum der Code nach File.Delete ausgeführt wird. Dies sollt erst geschehen, wenn die Datei tatsächlich gelöscht wurde.

Wie dem auch sei. Folgende Änderung vermeidet den Fehler:

if (File.Exists(filePath))
{
	File.Copy(filePath, filePath + ".backup", true);
}

Hiermit wird die aktuelle Datei-Version zum Backup kopiert und überschrieben, falls diese bereits vorhanden ist.

Im nächsten Schritt muss natürlich sicher gestellt werden, dass die aktuelle Datei-Version durch die Neue überschrieben wird.


Falsche Bild-Größen in WPF

Als WPF-Entwickler, der hin und wieder mit einem Designer zusammen arbeitet, wird man früher oder später mit dem Problem konfrontiert, dass die zugelieferten Grafiken in der Anwendung größer dargestellt werden, als sie abgespeichert wurden.

Dies wird immer dann auftreten, wenn ein Control die Größe des darin enthaltenen Bildes annehmen soll, zum Beispiel ein Button.

Ursache hierfür ist ziemlich sicher, dass der Designer die Grafiken mit einem Apple-Mac erstellt hat. Dieser arbeitet standardmäßig mit 72dpi für die Grafiken, während Microsoft in Windows 96dpi verwendet. Wird das Bild in WPF dargestellt, wird es um den Faktor 96/72 = 1,33 größer dargestellt.
Eine relativ schlechte Lösung wäre, die Bild-Größen statisch im Code zu hinterlegen. Spätere Größenänderung des Bildes müssten dann jedes Mal zusätzlich auch im Code nachgearbeitet werden.

Da ich in meinem Projekten recht häufig mit solchen Konstellationen arbeite, habe ich ein kleines UserControl entwickelt, dass sich diesem Problem annimmt. Beim Öffnen des Bildes wird die Orginalgröße ausgelesen und dem Bild zugewiesen. Der DPI-Wert wird ignoriert.

Das UserControl arbeitet aktuell nur mit einem BitmapSource als Source des Bildes, zum Beispiel eine Pfad zu einem Bild.

Folgend der Code für das Control:

public class DPIAwareImage : Image
{
	static DPIAwareImage()
	{
		// listen to changes to stretch property, and set default value to fill
		StretchProperty.OverrideMetadata(typeof(DPIAwareImage), new FrameworkPropertyMetadata(Stretch.Fill, new PropertyChangedCallback(StretchPropertyChanged)));
		/// listen to changes to source property
		SourceProperty.OverrideMetadata(typeof(DPIAwareImage), new FrameworkPropertyMetadata(new PropertyChangedCallback(SourcePropertyChanged)));
	}

	// check if a Stretch other then Fill is set.
	private static void StretchPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
	{
		if (args.NewValue == null)
		{
			return;
		}

		if ((Stretch)args.NewValue != Stretch.Fill)
		{
			throw new NotSupportedException("DPIAwareImage only works with Stretch=Fill.");
		}
	}

	// check the new image source and set the origin width and height if available
	private static void SourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
	{
		var source = args.NewValue as BitmapSource;

		if (source == null)
		{
			return;
		}

		var image = sender as DPIAwareImage;

		image.Stretch = Stretch.Fill;
		image.Width = source.PixelWidth;
		image.Height = source.PixelHeight;
	}
}

In den Zeilen 38 und 39 wird die Orginalgröße der Datenquelle ausgelesen und dem Bild zugewiesen.

Das Control überwacht neben Änderungen der Source-Eigenschaft auch die Stretch-Eigenschaft. Alles außer „Fill“ würde für den Verwendungszweck nicht funktionieren, da sich der möglicherweise „falsche“ DPI-Inhalt an die festgelegte Größe skalieren muss.


Länge/Dauer eines Videos mit MediaInfo ermitteln

Wer versucht die Länge eines Videos C# zu ermitteln, wird im Internet viele Lösungsvorschläge finden. Oftmals sind diese aber unvollständig, funktionieren nicht oder einfach nur unverständlich.

Ich zeige folgend die Nutzung der MediaInfo-Bibliothek.

Vorbereitung:

  1. Die aktuelle Version der MediaInfo-Bibliothek herunterladen
  2. Die MediaInfo.dll selbst, als auch den C# Wrapper in das eigene Projekt integrieren
  3. Die Video-Datei mit Hilfe des Wrappers öffnen, die Länge abfragen und wieder schließen

Download der MediaInfo-Bibliothek

  1. Auf der SourceForge-Seite den Reiter „Files“ auswählen
  2. In der Verzeichnisliste „binary“ auswählen
  3. „libmediainfo0“ auswählen
  4. Den obersten Eintrag/die aktuellste Version auswählen (z.B. „0.7.62“)
  5. Die passende Windows-Version „WithoutInstaller“ downloaden. Für ein 64bit System muss die x64 Version heruntergeladen werden, für ein 32bit die i386
    (z.B. „MediaInfo_DLL_0.7.62_Windows_x64_WithoutInstaller.7z“

Projektintegration

  1. Die heruntegeladene 7z-Datei mit 7-Zip entpacken
  2. Die MediaInfo.dll aus dem Hauptverzeichnis in das Visual-Studio-Projekt auf der Hauptebene integrieren
  3. Im Eigenschaftsfenster der MediaInfo.dll den „BuildVorgang“ auf „Inhalt“ und „In Ausgabeverzeichnis kopieren“ auf „Kopieren, wenn neuer“ stellen
    (Die MediaInfo.dll muss später parallel zur exe der Anwendung liegen.)
  4. In der Zip unter „Developers“->“Source“->“MediaInfoDll“ die „MediaInfoDLL.cs“ (C# Wrapper) an eine beliebige Stelle in das Visual-Studio-Projekt integrieren

Länge der Video-Datei abfragen

var mediaInfo = new MediaInfo();
mediaInfo.Open(fullPath);
var durationInMilliSecondsString = mediaInfo.Get(StreamKind.Video, 0, "Duration");
mediaInfo.Close();

if (durationInMilliSecondsString != null)
{
	var durationInMilliseconds = long.Parse(durationInMilliSecondsString);
}

Instanz aus Klassennamen erzeugen

Nehmen wir folgenden Fall an: Der Name einer Klasse liegt als string vor und es gilt eine Instanz der Klasse zu erzeugen.

Das kann zum Beispiel so funktionieren:

// you got the name of the class from somewhere
string className = "Message";
// create the type of the class using the destinated namespace
var type = Type.GetType("ValueObjects." + className, false); 
// create the instance now
var message = Activator.CreateInstance(type) as Message;

In ASP.Net funktioniert das allerdings nicht. Hier ist ein Workarund über den BuildManager (using System.Web.Compilation) notwendig:

// you got the name of the class from somewhere
string className = "Message";
// create the type of the class using the destinated namespace
var type = BuildManager.GetType("ValueObjects." + className, false);
// create the instance now
var message = Activator.CreateInstance(type) as Message;

SortedDictionary und ArgumentException

Neulich hatte ich einen sehr merkwürdigen Fehler bei der Verwendung eines SortedDictionary<string, string>. Ich erhielt eine ArgumentException mit der Beschreibung „Ein Eintrag mit dem gleichen Schlüssel ist bereits vorhanden.“

Das Ganze war umso verwunderlicher, da die Daten 1:1 aus einem normalen Dictionary<string, string> übernommen wurden und demzufolge der Fehler schon an dieser Stelle hätte auftreten müssen.

Um auf die Ursache des Fehlers zu kommen, habe ich folgende Testklasse geschrieben:

public class Sorter : IComparer
{
	public Sorter()
	{
		var sortedDictionary = new SortedDictionary<string, string>(this);
		sortedDictionary.Add("test", "Hallo Welt");
		sortedDictionary.Add("Test", "Hallo zurück");
	}

	public int Compare(string x, string y)
	{
		x = x.ToLower();
		y = y.ToLower();

		return x.CompareTo(y);
	}
}

Mach beachte die Zeile 12 und 13. Die Sortierung sollte case-insensitive erfolgen. Dies hat aber zur Folge, dass nicht der Angegebene Schlüssel „Test“ eingefügt, sondern der aus dem Vergleich, nämlich „test“. Ohne diese Zeilen würden der Fehler nicht auftreten.

Für mich ist dieses Verhalten nicht ganz nachvollziehbar und es fühlt sich ganz leicht nach einem Bug an. Denn obwohl die Vergleichsmethode den Wert „0“ für „gleich“ zurück gibt, sollte dennoch der ursprünglich angegebene Schlüssel verwendet werden.


Copyright © 2012-2019 Bernhard Krenz Alle Rechte vorbehalten.