November 2009 - Einträge
Ja ich weiß, ein WPF ListView um eine Sortierungsfunktion zu erweitern ist kein Hexenwerk, noch nicht einmal für jemanden, der noch nicht ganz so firm in der Entwicklung ist. Jedoch findet man im Netz sehr selten eine Implementierung, die das ganze über ein, oder für ein besseres Handling lieber zwei Attached Properties realisiert.
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace Framework.Helper
{
public class ListViewColumnSorter
{
#region Fields (2)
// Using a DependencyProperty as the backing store for SortDirection. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SortDirectionProperty =
DependencyProperty.RegisterAttached("SortDirection", typeof(SortDirection), typeof(ListViewColumnSorter), new UIPropertyMetadata(SortDirection.None, OnSortDirectionChanged));
// Using a DependencyProperty as the backing store for SortingPropertyName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SortingPropertyNameProperty =
DependencyProperty.RegisterAttached("SortingPropertyName", typeof(string), typeof(ListViewColumnSorter), new UIPropertyMetadata(String.Empty, OnPropertyChanged));
#endregion Fields
#region Methods (8)
// Public Methods (4)
public static SortDirection GetSortDirection(DependencyObject obj)
{
return (SortDirection)obj.GetValue(SortDirectionProperty);
}
public static string GetSortingPropertyName(DependencyObject obj)
{
return (string)obj.GetValue(SortingPropertyNameProperty);
}
public static void SetSortDirection(DependencyObject obj, SortDirection value)
{
obj.SetValue(SortDirectionProperty, value);
}
public static void SetSortingPropertyName(DependencyObject obj, string value)
{
obj.SetValue(SortingPropertyNameProperty, value);
}
// Private Methods (4)
static ICollectionView GetSource(object dataSource)
{
if (dataSource is CollectionViewSource)
return (dataSource as CollectionViewSource).View;
else
return CollectionViewSource.GetDefaultView(dataSource);
}
static void gvch_Click(object sender, RoutedEventArgs e)
{
switch (GetSortDirection(sender as DependencyObject))
{
case SortDirection.None:
SetSortDirection(sender as DependencyObject, SortDirection.Ascending);
break;
case SortDirection.Ascending:
SetSortDirection(sender as DependencyObject, SortDirection.Descending);
break;
case SortDirection.Descending:
SetSortDirection(sender as DependencyObject, SortDirection.None);
break;
default:
SetSortDirection(sender as DependencyObject, SortDirection.None);
break;
}
}
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
GridViewColumnHeader gvch = sender as GridViewColumnHeader;
if (gvch != null)
if (!string.IsNullOrEmpty(e.NewValue.ToString()))
gvch.Click += gvch_Click;
else
{
gvch.Click -= gvch_Click;
SetSortDirection(sender, SortDirection.None);
}
}
private static void OnSortDirectionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ListView lstView = ExtendetVisualTreeHelper.GetParent<ListView>(sender as DependencyObject);
ICollectionView view = GetSource(lstView.DataContext);
string pName = GetSortingPropertyName(sender);
for (int i = view.SortDescriptions.Count - 1; i > -1; i--)
{
if (view.SortDescriptions<img src="http://dotnet-forum.de/emoticons/emotion-55.gif" alt="Idea" />.PropertyName == pName)
view.SortDescriptions.RemoveAt(i);
}
if (lstView != null)
{
switch (GetSortDirection(sender as DependencyObject))
{
case SortDirection.Ascending:
view.SortDescriptions.Add(new SortDescription(pName, ListSortDirection.Ascending));
break;
case SortDirection.Descending:
view.SortDescriptions.Add(new SortDescription(pName, ListSortDirection.Descending));
break;
default:
break;
}
}
}
#endregion Methods
}
public enum SortDirection : int
{
None = 0,
Ascending,
Descending
}
}
der eigene enum, wird an dieser Stelle benötigt, da die .Net Enum ListSortDirection leider nur Ascending und Decending enthält, wir aber an dieser Stelle auch eine None benötigen.
Durch das zweite Atached Property, machen wir es an dieser Stelle nicht nur einfacher, sondern auch die aktuelle Sortierung, mit Hilfe von Templates und Triggern an der Oberfläche Visualisieren.
Der nächste Wurf vom Visual Studio, in der Version 2010 bringt viele neue große Features, jedoch können auch Kleinigkeiten einen Entwickler, wie mich erfreuen.
Das Redesign unserer aller geliebten IDE, bringt so eine Interessante Features hervor, wie ZB. bei der Interlisense. Das Matching der einzelnen Items, erfolgt nun nicht mehr nur vom Beginn an der jeweiligen Items, sondern nun auch Innerhalb des Wortes.
Man kann sich das ungefähr so vorstellen.
Das jeweilige Item, wird in die einzelnen Wortgruppen, anhand der groß und kleinschreibweise, gesplittet.
Aus beispielsweise RoutedCommand --> werden die beiden Wörter -- Routed und Command -- und
wenn man nun zb ‘Command’ schreibt würden in der Intelisense alle Items angezeigt bekommen die das Wort Command erhalten.
Doch es geht noch weiter, sobald man jetzt die jeweiligen Anfangsbuchstaben ‘ RC ‘ schreibt, zeigt mir die Intelisense ebenfalls das RoutedCommand
Ich weiß ich erkläre, das jetzt komisch, aber ladet euch doch einfach die BETA runter und spielt selber ein wenig rum.
Am Mittwoch den 25.11 wird Microsoft allem Anschein nach, die Deutsche Beta 2 des Visual Studio 2010 für die öffentlich zum Download anbieten, MSDN Abonnenten dürfen diese Version bereits seid dem 20 Herunterladen.
Wie auch die Englische Version, ist die Deutsche Version ebenfalls mit eine Go Live Lizenzausgestattet und kann somit auch bereits Produktiv eingesetzt werden.
Um in WPF mit Commands zu Arbeiten, gibt es unterschiedliche Möglichkeiten, an dieser Stelle möchte ich nun, 3 dieser Möglichkeiten Vorstellen.
1. Das Command durch Implementation des Interface ICommand
using System;
using System.Windows.Input;
namespace WpfApplication15
{
public class DummyCommmand: ICommand
{
public bool CanExecute(object parameter)
{
// Darf ich etwas machen
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
// Mache etwas
}
}
}
Dieses einfache Command kann dann, sobald er zB. in der Resource eines Windows definiert wurde, den einzelnen Command Properties, der jeweiligen Controls zugewiesen werden.
<Window x:Class="WpfApplication15.MainWindow"
xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">http://schemas.microsoft.com/winfx/2006/xaml</a>"
xmlns:local="clr-namespace:WpfApplication15"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:DummyCommmand x:Key="dummyCommand"/>
</Window.Resources>
<Grid>
<Button Content="Push" Command="{StaticResource dummyCommand}"/>
</Grid>
</Window>
Auf diese Weise können die Commands, innerhalb der gesamten Anwendung Verwendung finden. Je nach Ausprägung dieser Klasse, können auch unterschiedliche Properties Definiert werden, die das Control auf eine bestimmte Art und weise beeinflussen. zB. könnte ein Property Tooltip Implementiert werden, das dann an das jeweilige Steuerelement gebunden werden kann. So hätte man dann Anwendungsweit, immer ein nur Control, dass identisch aussieht sondern auch immer die gleiche Aktion ausführt.
2. Das Command in der Business Logik
Da das .Net Framework, uns bereits zwei, im im Namespace ‘System.Windows.Input’, vorimplementierte Commands, mit auf den Weg gibt, lassen sich damit auch sehr schnell, Commands innerhalb der Business Logik anlegen. Die beiden vorimplementierten Commands, sind zum einen das RoutedCommand und das RoutedUICommand, der unterschied liegt eigentlich nur in dem Property Text vom RoutedUICommand, dass sobald es an ein Control gebunden wird, deren Beschriftung beeinflussen kann.
using System.Windows.Input;
namespace WpfApplication15
{
public class DummyBL
{
public CommandBinding DummyCommand { get; set; }
public DummyBL()
{
DummyCommand = new CommandBinding(new RoutedUICommand("Dummy", "DummyCommand", typeof(DummyBL)));
DummyCommand.CanExecute += (sender, ex) => ex.CanExecute = true;
DummyCommand.Executed += DummyCommand_Executed;
}
void DummyCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
//mache etwas
}
}
}
using System.Windows;
namespace WpfApplication15
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
DummyBL dummyBl;
public MainWindow()
{
dummyBl = new DummyBL();
InitializeComponent();
this.CommandBindings.Add(dummyBl.DummyCommand);
this.DataContext = dummyBl;
}
}
}
<Window x:Class="WpfApplication15.MainWindow"
xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">http://schemas.microsoft.com/winfx/2006/xaml</a>"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Push" Command="{Binding DummyCommand.Command}"/>
</Grid>
</Window>
Mit dieser Implementierung, kann man ein Command definieren, dass sehr stark mit der Business Logik verknüpft ist und nur durch Databinding an die jeweilige Sicht und letztendlich auch an das Steuerelement gebunden wird.
3. eine eigene Klasse zur Gruppierung von Commands
Bei dieser Command Implementierung, erstellt man sich eine Sammlung aus logisch zusammenhängenden Commands eine Commandgruppe. Diese Gruppen können immer dann zum Einsatz kommen, wenn sich die Logik, hinter dem ausführendem Command, immer wieder ändert, jedoch zB. der Shortcut, Text oder möglicherweise auch Tooltip Anwendungsweit identisch sein soll.
Als erstes die Command Gruppe
using System.Windows.Input;
using System.Collections.Generic;
namespace App.Commands
{
public class Commands
{
#region Fields (1)
// Fields
private static Dictionary<CommandId, RoutedUICommand> _internalCommands = new Dictionary<CommandId, RoutedUICommand>();
#endregion Fields
#region Enums (1)
private enum CommandId : int
{
Save = 0,
}
#endregion Enums
#region Properties (1)
public static RoutedUICommand Save { get { return BuildCommand(CommandId.Save); } }
#endregion Properties
#region Methods (3)
// Private Methods (3)
private static RoutedUICommand BuildCommand(CommandId idCommand)
{
if (!_internalCommands.ContainsKey(idCommand))
_internalCommands.Add(idCommand, new RoutedUICommand(GetUIText(idCommand), GetPropertyName(idCommand), typeof(Commands), GetShortKey(idCommand)));
return _internalCommands[idCommand];
}
private static string GetPropertyName(CommandId commandId)
{
return commandId.ToString("g");
}
private static string GetUIText(CommandId commandId)
{
switch ((commandId))
{
case CommandId.Save:
return "Speichern";
default:
return "";
}
}
private static InputGestureCollection GetShortKey(CommandId commandId)
{
InputGestureCollection igc = new InputGestureCollection();
switch ((commandId))
{
case CommandId.Save:
igc.Add(new KeyGesture(Key.S, ModifierKeys.Control));
break;
}
return igc;
}
#endregion Methods
}
}
<Window x:Class="WpfApplication15.MainWindow"
xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">http://schemas.microsoft.com/winfx/2006/xaml</a>"
xmlns:command="clr-namespace:App"
Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
<CommandBinding Command="command:Commands.Save" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed"/>
</Window.CommandBindings>
<Grid>
<Button Content="{Binding Path=Command.Text, RelativeSource={RelativeSource Self}}" Command="command:Commands.Save"/>
</Grid>
</Window>
using System.Windows;
namespace WpfApplication15
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private void CommandBinding_CanExecute(object sender, System.Windows.Input.CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void CommandBinding_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
MessageBox.Show("Dieser Command wurde ausgeführt");
}
}
}
Auf diesem Wege erhalten wir Logische Command Gruppen, die jeweilige Implementierung kommt innerhalb der einzelnen Comands kann dann von Fall zu Fall unterschiedlich implementiert werden.
Um ein WPF Window, während der Entwicklung mal auf die schnelle zu testen, ist es nicht immer möglich sofort die Komplette Datenbank, an die Oberfläche zu koppeln.
Natürlich würde man sich an dieser Stelle wünschen, wenn man eine getrennte Entwicklungs Datenbank (zB. mit den Daten vom Vortag) zur Verfügung hätte, doch leider läuft nicht immer alles so Perfekt in der Softwareentwicklung und man brauch eine alternative. Für diesen Anwendungsfall sehe ich, zumindest für mich, eine Lösung in XML.
Vorteile
- XML Quellen können leicht erstellt und verändert werden
- die Änderungen im XAML sind übersichtlich
- Bei jedem Test, die gleichen Daten
<Window x:Class="WpfApplication14.MainWindow"
xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation"">http://schemas.microsoft.com/winfx/2006/xaml/presentation"</a
xmlns:x="<a href=">http://schemas.microsoft.com/winfx/2006/xaml"">http://schemas.microsoft.com/winfx/2006/xaml"</a
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<XmlDataProvider x:Key="xmlSource" XPath="/Kunden">
<x:XData>
<Kunden xmlns="">
<Kunde>
<Name>Lars</Name>
<Nachname>Schmitt</Nachname>
<Mail>Larsschmitt...de</Mail>
</Kunde>
<Kunde>
<Name>Muster</Name>
<Nachname>Muster</Nachname>
<Mail>Dummymuster...de</Mail>
</Kunde>
</Kunden>
</x:XData>
</XmlDataProvider>
<DataTemplate DataType="Kunde">
<TextBlock Text="{Binding XPath=Name}"/>
<TextBlock Text="{Binding XPath=Nachname}"/>
<TextBlock Text="{Binding XPath=Mail}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox DataContext="{StaticResource xmlSource}" ItemsSource="{Binding XPath=Kunde}"/>
</Grid>
</Window>
Die einzigsten Änderungen, die an dem XAML Code durchgeführt werden müssten, wären zum einem das Ändern der kompletten Datenquellen, sowie jedes einzelne Path auf XPath.
Im letzten Blog-post habe ich, über das neu hinzukommende Schlüsselwort dynamic berichtet.
Nachdem ich von verschiedenen Personen angesprochen wurde, dass Sie sich noch keinen wirklichen Anwendungsfall vorstellen können, habe ich mich Entschlossen, mal ein kleines Beispiel in IronRuby hier niederzuschreiben. (einmal mit und einmal ohne dynamic)
Ich entscheide mich, eine Anwendung Scriptingfähig zu machen, und beschließe die Scripts sollen in IronRuby sein. Nun habe ich also eine Datei wie diese hier und möchte die Methode Calc, in der Klasse Calculator aufrufen.
# Dateiname: source.rb
class Calculator
def Calc
21*2
end
end
der für diesen Aufruf nötige C# Code, läßt sich zwar mit und auch ohne dynamic sehr schnell hinschreiben jedoch finde ich, dass es mit dynamic sehr viel einfacher zu lesen ist.
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var engine = IronRuby.Ruby.CreateEngine();
engine.ExecuteFile("source.rb");
/* Ohne Dynamic würde das ganze so aussehen, und das was zurückgegeben wird ist ein Object */
//object calculator = engine.Execute("Calculator.new");
//Console.WriteLine(engine.Operations.InvokeMember(calculator, "Calc"));
/* und hier mit dynamic, zur Laufzeit habe ich als RückgabeTyp ein int und kann es auch behandeln, das heißt alle Eigenschaften von einem int, können ohne einen Cast benutzt werden */
dynamic calculator = engine.Execute("Calculator.new");
Console.WriteLine(calculator.Calc());
Console.ReadLine();
}
}
}
Das einzige was noch etwas ungewohnt ist, man hat verständlicherweise keine Intelisense!
Mit der neuen Version, von C# und dem Framework in der Version 4.0, zieht nun die Dynamik in das Leben eines C# Entwicklers ein.
Was heißt das nun?
…
Die Entwicklung wird halt ein Wenig Interessanter, und bietet uns nun neue Möglichkeiten.
Natürlich ist dieses Feature, in erster Linie eine Erweiterung in Richtung der Dynamischen Programmiersprachen und der COM Interoperabilität, da die Bindung bei diesem Schlüsselwort, an einen expliziten Typen erst zur Laufzeit anhand von definierten Regeln stattfindet, entfällt einerseits die Typenkonvertierung, aber auch das aufwendige benutzen der Reflection zB. beim aufrufen von Methoden.
Jedoch hat die Medaille wie so oft, abgesehen von der Performance auch noch eine Kehrseite, wie zB. in diesem Code.
namespace ConsoleApplication6
{
class Program
{
static void Main(string[] args)
{
dynamic dummyObj = new Dummy();
//das klappt noch ohne Probleme
dummyObj.Name = "Schmitt";
/* ohne dynamic würde sich dieser Code nicht Compilieren lassen
* jedoch mit dynamic, fällt dieser Fehler erst zu Laufzeit auf
*/
dummyObj.name = "Schmitt";
// Da der Typ, zum jetzigen Zeitpunkt, noch nicht bekannt ist, könnten aber auch nicht vorhandene Methoden aufgerufen werden.
string fullName = dummyObj.GetFullName();
}
}
public class Dummy :
{
public string Name { get; set; }
}
}

Die Rechteckauswahl mit [ALT] unterstütz Visual Studio, ja schon seit längerem.
Doch mit Visual Studio 2010, kann man nun auch endlich in mehreren Zeilen gleichzeitig schreiben.
Wie?
Man benutzt die Rechteckauswahl und Selektiert, entweder mit der [ALT] und der Linken Mousetaste und Selektiert zB. 4 untereinander liegende Zeilen.
alternativ kann man diese Selektion auch per [Alt] + [Shift] und den Cursor Tasten erstellt werden.
nun schreibt zum Beispiel einmal ‘public string Test {get; set;}’
…
das Ergebnis wäre dann
Dieses Feature, wird das Leben eines Entwicklers zwar nicht maßgeblich ändern, aber es kann einem doch so manches mal Arbeit ersparen.
Zumindest ich habe mir so ein Feature schon lange gewünscht.
Mehr Beiträge
Nächste Seite »