Biggle's Blog

Web- und Software Development

by Mario Binder

Wie verhindere ich beim Klick eines HyperlinkButtons in Silverlight die Umrandung – Quicky

Ja der Titel des Beitrags ist länger als der folgende “Silverlight-Quicky”. Aber was soll :)

Setzt man ein HyperlinkButton ein, so erscheint bei einem Klick aus diesen, ein nicht so toller Rahmen rundherum.

image

Um das zu verhindern, muss man die Property IsTabStop auf False setzen.

<HyperlinkButton Content="Mein Linktext" IsTabStop="False" ... />

Dann klappt’s auch ohne Rahmen Zwinkerndes Smiley


Viel Spaß beim entwickeln : )

by Mario Binder

Timeout im RIA-Service ändern

Bei einem WCF-Service ist man es gewohnt, Änderungen wie beispielsweise den Timeout in den Binding-Konfigurationen zu ändern.

Bei einem Silverlight-Projekt hatte ich nun das Problem, dass ich eine Berechnung im Service verwenden wollte, die jedoch mehr als die eingestellten 60Sekunden benötigt. Ein Binding wie man das nun von WCF kennt, ist mit einem RIA-Service erst einmal so nicht möglich.

Um die Timeout-Einstellungen nun trotzdem im RIA-Service anzupassen, muss man etwas Hand anlegen.

Man geht dazu wie folgt vor:

Im Silverlight-Projekt lasse ich mir alle Dateien anzeigen und finde im Order “Generated Code” meine Klasse die mit “Web.g.cs” endet.

image

In dieser, suche ich meinen Kontext den ich verwende und finde auch den dazugehörigen Contract:

image

Nun erstelle ich in meinem Silverlight-Projekt die partielle Klasse mit den selben Namen, wie der in meinem Kontext.

In dieser Klasse implementiere ich dann die Methode OnCreated() und kann meinem ChannelFactory-Endpoint-Binding ein Timeout programmatisch zuweisen:

public partial class TcmdbContext { partial void OnCreated() { ((WebDomainClient<Tcmdb.Web.Services.TcmdbContext.ITcmdbServiceContract>)this.DomainClient) .ChannelFactory.Endpoint.Binding.SendTimeout = new TimeSpan(0, 15, 0); } }

Wichtig dabei ist, dass man sich im selben Namespace wie der der generierte Klasse befindet!

by Mario Binder

Selektierte TextBox in Silverlight

Wenn man den Text in einer TextBox  vorselektieren möchte, sobald man dort hineinklickt, so kann man folgendes CodeSnippet dazu verwenden:

myTextBox.GotFocus += (s, arg) => { resultTextBox.SelectAll(); };

Eigene TextBox-Klasse

Oder man macht’s gleich richtig und baut sich eine eigene Klasse, namens “GotFocusTextBox”.

using System.Windows.Controls; namespace MPSoft.Controls { public class GotFocusTextBox : TextBox { public GotFocusTextBox() { this.GotFocus += (s, arg) => { this.SelectAll(); }; } } }

Verwendung:

xmlns:MPControls="clr-namespace:MPSoft.Controls" <MPControls:GotFocusTextBox Text="{Binding any"} />

Selektierten Text in die Zwischenlage kopieren

Nun möchte man vielleicht noch bestimmen, ob man den vorselektierten Text in der Textbox gleich in die Zwischenablage kopieren möchte. Hier erstellt man sich in der GotFocusTextBox-Klasse ein Property namens “CopyFocusedTextToClipBoard” dafür. 

public bool CopyFocusedTextToClipBoard { get; set; }

Nun, um den Text in die Zwischenablage zu bekommen, muss man in Silverlight über einen Umweg gehen. Hier kann man leider nicht die Clipboard-Klasse aus dem Framework verwenden. Die Sicherheitsrichtlinien vom Browser machen hier leider nicht mit. Aber man kann über Umwege dennoch an den Text, wie folgende Implementierung zeigt:

public class ClipBoard { /// <summary> /// Sets the text to clipboard. /// </summary> /// <param name="value">The value.</param> public void SetTextToClipboard(string value) { if (!String.IsNullOrWhiteSpace(value)) { var clipboardData = (ScriptObject)HtmlPage.Window.GetProperty("clipboardData"); if (clipboardData != null) clipboardData.Invoke("setData", "text", value); } } }

(unterstützt nicht jeder Browser, aber beim IE funktioniert das so).

Der Konstruktor der GotFocusTextBox-Klasse sieht demnach wie folgt aus:

public GotFocusTextBox() { this.GotFocus += (s, arg) => { this.SelectAll(); if (CopyFocusedTextToClipBoard) new ClipBoard().SetTextToClipboard(this.Text); }; }

Nun im Xaml noch die Property setzen und bei true wird dann entsprechend der Text in die Zwischenablage kopiert.

<MPControls:GotFocusTextBox CopyFocusedTextToClipBoard="true" Text="{Binding any}" />

fertig.

 

Viel Spaß beim entwickeln : )

by Mario Binder

UpdateSource-Trigger für Silverlight ComboBox

Im vorherigen Artikel hab ich die Klasse BindingHelper von Thomas vorgestellt. Diese ermöglich ein UpdateSourceTrigger “OnPropertyChanged” an einer TextBox in Silverlight.

Ich habe die Klasse mal für eine ComboBox erweitert.

/// <summary> /// Supports a PropertyChanged-Trigger for DataBindings /// in Silverlight. Works just for TextBoxes /// (C) Thomas Claudius Huber 2009 /// http://www.thomasclaudiushuber.com /// extended from Mario Priebe 2011 for ComboBox SelectedItem /// http://www.biggle.de /// </summary> public class BindingHelper { public static bool GetUpdateSourceOnChange(DependencyObject obj) { return (bool)obj.GetValue(UpdateSourceOnChangeProperty); } public static void SetUpdateSourceOnChange(DependencyObject obj, bool value) { obj.SetValue(UpdateSourceOnChangeProperty, value); } // Using a DependencyProperty as the backing store for … public static readonly DependencyProperty UpdateSourceOnChangeProperty = DependencyProperty.RegisterAttached("UpdateSourceOnChange", typeof(bool), typeof(BindingHelper), new PropertyMetadata(false, OnPropertyChanged)); /// <summary> /// Called when [property changed]. /// </summary> /// <param name="obj">The obj.</param> /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param> private static void OnPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { if (obj is ComboBox) { var cb = obj as ComboBox; if ((bool)e.NewValue) cb.SelectionChanged += (cb_SelectionChanged); else cb.SelectionChanged -= (cb_SelectionChanged); } else if (obj is TextBox) { var txt = obj as TextBox; if ((bool)e.NewValue) txt.TextChanged += OnTextChanged; else txt.TextChanged -= OnTextChanged; } } /// <summary> /// Called when . /// </summary> /// <param name="sender">The sender.</param> /// <param name="e">The <see cref="System.Windows.Controls.TextChangedEventArgs"/> instance containing the event data.</param> static void OnTextChanged(object sender, TextChangedEventArgs e) { var txt = sender as TextBox; if (txt == null) return; var be = txt.GetBindingExpression(TextBox.TextProperty); if (be != null) be.UpdateSource(); } /// <summary> /// Handles the SelectionChanged event of the combobox control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param> static void cb_SelectionChanged(object sender, SelectionChangedEventArgs e) { var cb = sender as ComboBox; if (cb == null) return; var be = cb.GetBindingExpression(ComboBox.SelectedItemProperty); if (be != null) be.UpdateSource(); } }

Im gebundenen ViewModel definiere ich ein Property vom Typ, welcher in der ComboBox verwendet wird.

private TourData tourFilter; public TourData TourFilter { get { return tourFilter; } set { tourFilter = value; OnPropertyChanged("TourFilter"); } }

Verwendet wird das Ganze dann wie folgt:

<ComboBox ItemsSource="{Binding ElementName=TourDataDomainContext, Path=Data}" DisplayMemberPath="TourName" SelectedValuePath="TourId" SelectedItem="{Binding Source={StaticResource DailyStatsViewModel}, Path=TourFilter, Mode=TwoWay}" helper:BindingHelper.UpdateSourceOnChange="True" />

Viel Spaß beim entwickeln : )

by Mario Binder

OR verknüpftes Filtering in einem DataGrid

Im folgenden Artikel beschreibe ich, wie man in einem DataGrid OR verknüpftes Filtering mit Daten aus einem RIA Sevice nach MVVM implementiert. Da sich keine OR Verknüpfung an einem “normalen” WPF DataGrid “so auf die Schnelle” umsetzen lässt, setze ich ein Telerik DatatGrid ein.

Um in einem DataGrid die Daten aus einem RIA Service-Kontext über der Eingabe in einer TextBox zu filtern, kann man normalerweise den FilterDescriptor einsetzen:

<riaControls:DomainDataSource.FilterDescriptors>     <riaControls:FilterDescriptor PropertyPath="Ort" Operator="Contains" Value="{Binding ElementName=txtOrt, Path=Text}" /> </riaControls:DomainDataSource.FilterDescriptors>

Das funktioniert soweit auch ganz gut, jedoch kann es dazu kommen, dass der Kunde über diese TextBox in mehr als einer Spalte im DataGrid die Daten filtern möchte.

Beispiel: Ich habe eine Spalte "Ort" und eine Spalte "Halle". Nun möchte ich über meiner TextBox entweder in Ort oder Halle filtern und das Ergebnis im Grid anzeigen.

Der o.g. Code zeigt, wie man einen Filter der Liste von FilterDescriptors hinzufügt. Natürlich heißt das auch, dass ich hier mehr als diesen verwenden kann. Nur das Problem ist, das alle Filter innerhalb dieser Liste, lediglich AND verknüpft werden. Das Verwenden eines OR Operators scheint leider (“auf die Schnelle”) nicht möglich.

Wir haben das Glück Komponenten von Telerik einzusetzen. Auch hier ist es möglich am DataGrid (RadGridView) Filter zu verwenden. Hier hat man die Collection "FilterDescriptors" zur Verfügung, in der ich mehrere "CompositeFilterDescriptor" definieren kann. Das Gute daran, hier kann ich über die Property LogicalOperator bestimmen, wie die Filter zueinander stehen.

Das Ganze sieht dann beispielsweise wie folgt aus:

<telerik:RadGridView.FilterDescriptors>     <telerik:CompositeFilterDescriptor LogicalOperator="Or">         <telerik:CompositeFilterDescriptor.FilterDescriptors>             <telerik:FilterDescriptor Member="Ort"                     Operator="Contains"                     Value="hier der suchstring"                     IsCaseSensitive="False" />             <telerik:FilterDescriptor Member="Halle"                     Operator="Contains"                     Value="hier ein weiterer suchstring"                     IsCaseSensitive="False" />         </telerik:CompositeFilterDescriptor.FilterDescriptors>     </telerik:CompositeFilterDescriptor> </telerik:RadGridView.FilterDescriptors>

Nun möchte ich gerne den Wert aus der "Filter"-TextBox in den Filtern verwenden. In etwa so:

<telerik:FilterDescriptor Member="Ort"         Operator="Contains"         Value="{Binding ElementName=txtOrt, Path=Text}"         IsCaseSensitive="False" />

 

Leider funktioniert das so nicht. Telerik sagt hier :

"Binding with the ElementName in the Silverlight GridView is not the best way to go. ElementName uses the logical tree to find the element in the element name binding. Parts of the telerik grid (and the sl data grid), do not participate in this part of the tree, or they have not been created yet at the time of binding (since most of the grid is dynamically generated)"

Jedoch ist es mir möglich über ein ViewModel-Property dieses Problem zu umgehen.

Ich erstelle mir dazu eine ViewModel-Klasse (wenn noch nicht vorhanden) und definiere in dieser die Property "SearchString". (Das ViewModel muss das Interface "INotifyPropertyChanged" implementieren)

private string searchString; public string SearchString {     get     {         return searchString;     }     set     {         searchString = value;         this.OnPropertyChanged("SearchString");     } }

Anschließend referenzieren wir das ViewModel in unserer View:

xmlns:vm="clr-namespace:Namespace.zum.ViewModel"

und in den Page oder Grid Resources instanziieren wir dieses:

<vm:MyViewModel x:Key="MyViewModel" />

Jetzt binden wir TextBox-Text an die Property "SearchString"

<TextBox Text="{Binding Source={StaticResource MyViewModel}, Path=SearchString, Mode=TwoWay}" Name="txtOrt" />

Und dasselbe machen wir in den FilterDescriptoren:

<telerik:FilterDescriptor Member="Ort"     Operator="Contains"     Value="{Binding Source={StaticResource MyViewModel}, Path=SearchString}"     IsCaseSensitive="False" /> <telerik:FilterDescriptor Member="Halle"     Operator="Contains"     Value="{Binding Source={StaticResource MyViewModel}, Path=SearchString}"     IsCaseSensitive="False" />

Funktioniert :-)

Aber Achtung: Wenn man nun in der TextBox Werte die in der Spalte "Ort" und "Halle" vorkommen einträgt, muss man zunächst den Focus der TextBox verlassen, um das Ergebnis zu sehen. Ich möchte aber gerne, das ich das Ergebnis direkt bei der Eingabe bekomme.

In WPF ist es möglich den UpdatSourceTrigger auf PropertyChanged zu setzen. In Silverlight finde ich lediglich default und explizit. Hmmm, was nun. Google angeschmissen und erfahren, das gibt es so nicht.

Auf der Suche nach einer Lösung sogleich beim .NET Rocker gelandet. Thomas stellt eine Klasse bereit, welche das PropertyChanged in Silverlight bereit stellt.

/// <summary> /// Supports a PropertyChanged-Trigger for DataBindings /// in Silverlight. Works just for TextBoxes /// (C) Thomas Claudius Huber 2009 /// http://www.thomasclaudiushuber.com /// </summary> public class BindingHelper {     public static bool GetUpdateSourceOnChange(DependencyObject obj)     {         return (bool)obj.GetValue(UpdateSourceOnChangeProperty);     }     public static void SetUpdateSourceOnChange         (DependencyObject obj, bool value)     {         obj.SetValue(UpdateSourceOnChangeProperty, value);     }     // Using a DependencyProperty as the backing store for …     public static readonly DependencyProperty         UpdateSourceOnChangeProperty = DependencyProperty.RegisterAttached("UpdateSourceOnChange",         typeof(bool), typeof(BindingHelper), new PropertyMetadata(false, OnPropertyChanged));     private static void OnPropertyChanged         (DependencyObject obj,         DependencyPropertyChangedEventArgs e)     {         var txt = obj as TextBox;         if (txt == null)             return;         if ((bool)e.NewValue)         {             txt.TextChanged += OnTextChanged;         }         else         {             txt.TextChanged -= OnTextChanged;         }     }     static void OnTextChanged(object sender,         TextChangedEventArgs e)     {         var txt = sender as TextBox;         if (txt == null)             return;         var be = txt.GetBindingExpression(TextBox.TextProperty);         if (be != null)         {             be.UpdateSource();         }     } }

Wenn wir diese verwenden, so wird nun beim TextChanged-Event wie erwartet das Grid aktualisiert.

<TextBox Text="{Binding Source={StaticResource MyViewModel}, Path=SearchString, Mode=TwoWay}" Name="txtOrt" />

Jetzt passt das.

Viel Spaß beim entwickeln : )

by Mario Binder

Text innerhalb eines TextBlock-Controls formatieren – XAML

Um einen Text innerhalb einer Textbox zu formatieren, steht einem die Klasse Run zur Verfügung. Run ist ein Element für fortlaufenden Inhalt auf Inlineebene, das formatierten oder unformatierten Lauftext enthalten kann.

1
2
3
4
<TextBlock FontSize="28" TextWrapping="Wrap">
    Hier steht <Run Foreground="Red">Text</Run> der
    <Run FontWeight="Bold" FontStyle="Italic" Foreground="Cyan" FontSize="38" TextDecorations="Underline">verschiedene Formate</Run> darstellt.
</TextBlock>

Einen Zeilenumbruch innerhalb eines Textblocks erreicht man mit

<LineBreak />

Bei einem Hyperlink kann diese Methode auch angewendet werden. (In Silverlight HyperlinkButton)

1
2
3
4
5
6
7
<TextBlock TextWrapping="Wrap">            
    <Hyperlink TextDecorations="None" NavigateUri="http://www.biggle.de">
        <Italic>Hier</Italic> <Bold>steht</Bold> ein <Underline>Hyperlink</Underline>                 
        <LineBreak />      
            über mehrere <Run Foreground="Red">Zeilen</Run>.
    </Hyperlink>
</TextBlock>

Im Hyperlinkbeispiel sieht man auch, wie man den Text kursiv,  fett geschrieben und unterstrichen, noch formatieren kann.

Selbst Bilder können so in einem TextBlock mit dargestellt werden:

1
2
3
4
5
<TextBlock>
    Das bin ich
    <LineBreak />
    <Image Source="/Images/IMG_6911_small.jpg" Width="50" />
</TextBlock>

Es ist auch möglich, innerhalb des Textblock-Controls Werte aus einem Binding heraus darzustellen:

1
2
3
4
5
6
7
8
9
<TextBlock DataContext="{Binding Order}" TextWrapping="Wrap">
    <Run TextDecorations="Underline">Ihre Bestellung</Run>
    <LineBreak />
    <Run>Produkt:</Run>
    <TextBlock Text="{Binding productName}" />
    <Run>(</Run>
        <TextBlock Text="{Binding quantity}" />
    <Run>)</Run>
</TextBlock>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public partial class MainWindow : Window
{
    public Order Order { get; set; }
 
    public MainWindow()
    {
        InitializeComponent();
        LoadOrders();
    }  
    private void LoadOrders()
    {
        Order = new Order();
        Order.productName = "Mountainbike";
        Order.quantity = 1;
 
        this.DataContext = this;
    }
}


Viel Spaß beim entwickeln : )