In der Praxis kann es vorkommen, dass gebundene Eigenschaften mal nicht eben einen Wert zurück liefern, sondern durchaus länger benötigen. Dies tritt beispielsweise auf, wenn

  • Images (oder Listen davon) geladen werden
  • Daten von einem Service abgeholt werden
  • Längere Berechnungen durchzuführen sind

Sicherlich ließen sich weitere Punkte finden. Jetzt wäre es jedoch für den Benutzer unangenehm, die Oberfläche zu sperren und ihm keine Rückmeldung zu liefern, dass gerade seine Anforderung ausgeführt wird. Um dies zu bewerkstelligen, gibt es seit je her PriorityBinding. Darüber können mehrere Bindungen inklusive Priorität festgelegt werden. Die Priorität wird dabei in der Reihenfolge der Bindungs-Angaben bestimmt. Die erste Bindung hat die höchste Priorität.

Beispiele zum Thema PriorityBinding gibt es einige. Die meisten arbeiten jedoch mit einem Thread.Sleep und sind gerade für Einsteiger daher nicht so einfach auf die Realität um zu legen. Das nachfolgend gezeigte Beispiel folgt zwar ebenso einem akademischen Ansatz, kommt jedoch ohne Thread.Sleep aus und sollte die Funktionsweise daher – hoffentlich – besser beschreiben.

Beispiel

Die hier gezeigte Anwendung bietet die Möglichkeit, einen Wert einzugeben, auf dessen Basis der letzte Wert der Fibonacci-Reihe errechnet wird. Ausgeführt wird die Berechnung, wenn auf die angebotene Schaltfläche geklickt wird. Die nachfolgende Abbildung zeigt die sehr einfache Oberfläche. Während der Berechnung soll der Anwender als Resultat eine Meldung erhalten, dass die Berechnung in Gange ist bzw. schlussendlich das tatsächliche Ergebnis.

image

Implementierung

Zu Grunde liegt dieser Anwendung die von mir bereits in mehreren Artikeln angesprochene MVVM-Implementierung. D.h. es wird versucht, an dieser Stelle eine saubere Auftrennung der Zuständigkeiten zu erreichen.

Die Bereitstellung der notwendigen Eigenschaften, Berechnungen und Aktionen (siehe Command) übernimmt ein ViewModel:

[LocatorAttribute("MainViewModel")]

public class MainViewModel : ViewModelBase

{

    private string calculationMessage = "Calculating result ...";

    private int inputValue;

    private RelayCommand calculateCommand;

 

    public string CaluculationMessage

    {

        get { return calculationMessage; }

    }

 

    public int InputValue

    {

        get { return inputValue; }

        set

        {

            if (inputValue == value)

                return;

            inputValue = value;

            RaisePropertyChanged("InputValue");

        }

    }

 

    public int Result

    {

        get { return CalcFibonacci(InputValue); }

    }

 

    private int CalcFibonacci(int x)

    {

        if (x <= 1)

        {

            return 1;

        }

        return CalcFibonacci(x - 1) + CalcFibonacci(x - 2);

    }

 

    public ICommand CalculateCommand

    {

        get

        {

            if (calculateCommand == null)

                calculateCommand = new RelayCommand(Calculate);

            return calculateCommand;

        }

    }

 

    private void Calculate(object param)

    {

        RaisePropertyChanged("CaluculationMessage");

        RaisePropertyChanged("Result");

    }

}

Die Eigenschaft CalculationMessage liefert einen String zurück, der anzeigt, dass die Berechnung aktuell in Gange ist. Result selbst ruft eine Methode auf, welche den Fibonacci berechnet und das Ergebnis zurück liefert. Diese Berechnung kann nun, abhängig vom Eingangswert, recht lange dauern. Der implementierte und nach aussen gegebene Command löst lediglich ein PropertyChanged der beschriebenen Eigenschaften aus (dadurch wird beim Aktualisierung der Bindung auch die Berechnung durchgeführt).

Nun werfen wir einen Blick auf die View:

<Grid>

    <Grid.ColumnDefinitions>

        <ColumnDefinition Width="Auto"/>

        <ColumnDefinition Width="*"/>

    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>

        <RowDefinition Height="Auto"/>

        <RowDefinition Height="Auto"/>

        <RowDefinition Height="Auto"/>

        <RowDefinition Height="Auto"/>

        <RowDefinition Height="Auto"/>

    </Grid.RowDefinitions>

 

    <TextBlock Text="PriorityBinding Sample" 

                Style="{StaticResource HeaderBlockStyle}" 

                Grid.ColumnSpan="2"/>

        

    <TextBlock Text="Fib of" Grid.Row="2"/>

    <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="1">

        <TextBox Text="{Binding InputValue}"/>    

        <TextBlock Text="Enter at least a value > 30"/>

    </StackPanel>

        

    <TextBlock Text="Result" Grid.Row="3"/>

    <TextBlock Grid.Column="1" Grid.Row="3">

        <TextBlock.Text>

            <PriorityBinding>

                <Binding Path="Result" IsAsync="True" Mode="OneWay"/>

                <Binding Path="CaluculationMessage" Mode="OneWay"/>

            </PriorityBinding>

        </TextBlock.Text>

    </TextBlock>

        

    <Button Content="Calculate" 

            Command="{Binding CalculateCommand}" 

            Grid.Row="4" 

            Grid.ColumnSpan="2"/>

</Grid>

Von besonderer Bedeutung ist hier das TextBlock-Element, welches für die Darstellung des Ergebnisses zuständig ist:

<TextBlock Grid.Column="1" Grid.Row="3">

    <TextBlock.Text>

        <PriorityBinding>

            <Binding Path="Result" IsAsync="True" Mode="OneWay"/>

            <Binding Path="CaluculationMessage" Mode="OneWay"/>

        </PriorityBinding>

    </TextBlock.Text>

</TextBlock>

Hier wird für die Eigenschaft Text ein PriorityBinding angegeben. Die höchste Priorität besitzt die Bindung an die Eigenschaft Result des ViewModels. Diese Bindung erfolgt asynchron (da sie etwas länger dauern kann). Die zweite Bindung erfolgt auf unsere Eigenschaft, welche rückmeldet, dass der Berechnungsvorgang in Arbeit ist.

Geben wir in der ausgeführten Anwendung einen größeren Wert ein, liefert das erste Binding beim ersten Zugriff keinen Wert zurück. Daher wird das nächste Binding in der Liste herangezogen. Dieses liefert sofort einen Wert zurück (es muss nur ein String zurück gegeben werden). Steht das Ergebnis zur Verfügung, wird dieses nachgereicht und die Statusmeldung verschwindet.

Hinweis: DependencyProperty.UnsetValue gilt nicht als erfolgreicher Rückgabewert.

Es besteht auch noch die Möglichkeit die Eigenschaft FallbackValue der PriorityBinding zu setzen. Dies würde den Weg über die zusätzliche Eigenschaft im ViewModel sparen. Wer jedoch mehr Kontrolle über den angezeigten Wert benötigt, wird den gezeigten Weg wählen.

Download

Dieses Beispiel steht untenstehend als Download zur Verfügung.

Über den Autor

Norbert Eder

Ich bin ein leidenschaftlicher Softwareentwickler und Fotograf. Mein Wissen und meine Gedanken teile ich nicht nur hier im Blog, sondern auch in Fachartikeln und Büchern.