ScrollBars

  • WPF
  • .NET (FX) 4.5–4.8

Es gibt 10 Antworten in diesem Thema. Der letzte Beitrag () ist von Lightsource.

    Ich wollte mich tatsächlich mal mit WPF beschäftigen. ^^

    Ok, kein NumericUpDown =O
    Dann probier ich's halt mit Scrollbars, die ich irgendwie an jeweils eine TextBox hänge.

    Nun seltsam, TextBox gibt es als Type aber ScrollBar nicht? ;(

    Und dann, ich möchte ein kleines Tool für WPF umschreiben. Da konnte man in fünf NumericUpDown Controls jeweils den Wert ändern, und die anderen 4 werden dann über verschiedene Berechnungen angepasst.
    Um nicht in einer Eventschleife zu landen, hatte ich mit Add/RemoveHandler gearbeitet, und die jeweils nicht zu berechnenden Controls von den Events abgetrennt.

    Wie kann man so etwas mit WPF machen? Ich muss alle, in meinem Fall nun Scrollbars, über ihren Namen erkennen, um dann die Eventkette der nicht geänderten Scrollbars abschalten, bis ich
    die Ergebnisse in allen Textboxen und Scrollbars übertragen habe.

    Ich hoffe ich habe mich deutlich genug ausgedrückt.
    Es gibt zahlreiche Frameworks für WPF, u.a. z.B. MahApps.Metro und MaterialDesignForXaml.

    Diese Frameworks bieten zahlreiche Controls in modernen Designs.

    mahapps.com/
    materialdesigninxaml.net/

    Und hier gleich noch eine riesige Liste mit viel, viel mehr: github.com/Carlos487/awesome-wpf

    Da musst du dich einfach mal umschauen. Es ist aber auch nicht sehr schwer, seine eigenen Controls in WPF zu basteln. Muss man sich nur einmal mit auseinandergesetzt haben.
    Quellcode lizensiert unter CC by SA 2.0 (Creative Commons Share-Alike)

    Meine Firma: Procyon Systems

    Selbstständiger Softwareentwickler & IT-Techniker.

    Lightsource schrieb:

    Ich muss alle, in meinem Fall nun Scrollbars, über ihren Namen erkennen, um dann die Eventkette der nicht geänderten Scrollbars abschalten, bis ich
    die Ergebnisse in allen Textboxen und Scrollbars übertragen habe.

    Ich hoffe ich habe mich deutlich genug ausgedrückt.
    ähm... nicht wirklich.
    Aber soviel vermeine ich zu verstehen, dass ich dich auf dem kompletten Holzweg vermute.
    Keinesfalls musst du Scrollbars über ihren Namen erkennen und abschalten.

    Für mich klingt das, als bräuchtest du ein UserControl, welches sich als DataTemplate für ein bestimmtes Viewmodel eignet.
    Alles was ich verstanden habe ist, dass dieses UserControl 9 Scrollbars haben wird, wobei vier iwie mit fünf anderen interagieren.
    Diese Info gibt natürlich nichts her über das Viewmodel - tja, aber das Viewmodel ist genau des Pudels Kern...
    Ich habe inzwischen in einem Buch einen Ersatz für ScrollBars gefunden, den ich nehmen kann.

    Nochmal zu der anderen Frage:
    Ich hatte ja das Programm mit NumericUpdowns bereits funktionsfertig im Einsatz.
    Da ich WPF ein wenig üben wollte, bin ich jetzt halt am Suchen, wie ich bestimmte Sachen umsetzen kann,
    um mich vielleicht noch an größere Sachen zu wagen.

    Durch das Klicken auf ein NUD-Control wird dessen Zahlenwert verändert. Diese Veränderung wird als Event erfasst.
    der Zahlenwert wird mittels versch. Formeln zu mehreren Ergebnissen umgerechnet. Diese Ergebnisse
    werden dann vom Programm in die anderen NUDs eingetragen.
    Da dadurch aber eine Veränderung in diesen NUDs stattfindet, käme es zu einem Event, das nun
    wieder die Berechnung auslösen würde, um in den anderen NUDs die Ergebnisse einzutragen.
    Um das zu vermeiden, habe ich die Events zwischendrin still gelegt, so dass es nicht zu
    einem gegenseitigen Feuern kommt. Das habe ich mit RemoveHandler gemacht.

    Alle NUDs-Events werden von einer einzigen Ereignisroutine bearbeitet, in der ich über
    "sender".name feststelle, welcher NUD gerade gefeuert hat. Über Case/Select
    werden dann die verschiedenen Berechnungen gestartet.

    Nun habe ich mich gefragt, ob ich bei WPF diese AddHandler und RemoveHandler
    genau so nutzen kann wie bei Winforms, oder ob es dazu andere Methoden gibt.

    Nun habe ich mich gefragt, ob ich bei WPF diese AddHandler und RemoveHandler
    genau so nutzen kann wie bei Winforms, oder ob es dazu andere Methoden gibt.


    Geht beides, wenn das Projekt größer ist würde ich mit MVVM-Pattern arbeiten bei kleineren Projekten kann der MVVM Aufwand den Nutzen schnell übersteigen. Wenn du dich in WPF einarbeiten willst würde ich dir zu einer MVVM varinaten raten.
    Es hat tatsächlich mit AddHandler etc. geklappt. Da sind noch ein paar Kleinigkeiten, aber es reagiert zumindest richtig.

    Die NumericUpDowns habe ich mit RepeatButton simuliert. Zur Ein- und Ausgabe wird eine TextBox verwendet.

    Wie kann ich eine zusätzliche Eigenschaft in die TextBox/RepeatButton Kombination einfügen?
    Ich würde gerne einen maximalen und minimalen Wert vorgeben, der nicht überschritten werden sollte.
    Natürlich kann ich das auch im Code machen, aber es interessiert mich wie das in XAML geht.
    Da brauchst du ein AttachedProperty aber dann könntest du dir auch gleich eine NumericUpDown basteln.

    NumericUpDown



    NumericUpDown.cs

    C#-Quellcode

    1. using CustomControls.Commands;
    2. using System.Windows;
    3. using System.Windows.Controls;
    4. using System.Windows.Input;
    5. using System.Windows.Media;
    6. namespace CustomControls
    7. {
    8. /// <summary>
    9. /// Führen Sie die Schritte 1a oder 1b und anschließend Schritt 2 aus, um dieses benutzerdefinierte Steuerelement in einer XAML-Datei zu verwenden.
    10. ///
    11. /// Schritt 1a) Verwenden des benutzerdefinierten Steuerelements in einer XAML-Datei, die im aktuellen Projekt vorhanden ist.
    12. /// Fügen Sie dieses XmlNamespace-Attribut dem Stammelement der Markupdatei
    13. /// an der Stelle hinzu, an der es verwendet werden soll:
    14. ///
    15. /// xmlns:MyNamespace="clr-namespace:NumericUpDown"
    16. ///
    17. ///
    18. /// Schritt 1b) Verwenden des benutzerdefinierten Steuerelements in einer XAML-Datei, die in einem anderen Projekt vorhanden ist.
    19. /// Fügen Sie dieses XmlNamespace-Attribut dem Stammelement der Markupdatei
    20. /// an der Stelle hinzu, an der es verwendet werden soll:
    21. ///
    22. /// xmlns:MyNamespace="clr-namespace:CustomControls;assembly=CustomControls"
    23. ///
    24. /// Darüber hinaus müssen Sie von dem Projekt, das die XAML-Datei enthält, einen Projektverweis
    25. /// zu diesem Projekt hinzufügen und das Projekt neu erstellen, um Kompilierungsfehler zu vermeiden:
    26. ///
    27. /// Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Zielprojekt und anschließend auf
    28. /// "Verweis hinzufügen"->"Projekte"->[Dieses Projekt auswählen]
    29. ///
    30. ///
    31. /// Schritt 2)
    32. /// Fahren Sie fort, und verwenden Sie das Steuerelement in der XAML-Datei.
    33. ///
    34. /// <MyNamespace:NumericUpDown/>
    35. ///
    36. /// </summary>
    37. public class NumericUpDown : Control
    38. {
    39. public decimal Value
    40. {
    41. get => (decimal)GetValue(ValueProperty);
    42. set => SetValue(ValueProperty, value);
    43. }
    44. public static readonly DependencyProperty ValueProperty;
    45. public decimal MinValue
    46. {
    47. get => (decimal)GetValue(MinValueProperty);
    48. set => SetValue(MinValueProperty, value);
    49. }
    50. public static readonly DependencyProperty MinValueProperty;
    51. public decimal MaxValue
    52. {
    53. get => (decimal)GetValue(MaxValueProperty);
    54. set => SetValue(MaxValueProperty, value);
    55. }
    56. public static readonly DependencyProperty MaxValueProperty;
    57. public decimal Step
    58. {
    59. get => (decimal)GetValue(StepProperty);
    60. set
    61. {
    62. if (value < 0)
    63. value = 0.1M;
    64. SetValue(StepProperty, value);
    65. }
    66. }
    67. public static readonly DependencyProperty StepProperty;
    68. public Brush ButtonForeground
    69. {
    70. get => (Brush)GetValue(ButtonForegroundProperty);
    71. set => SetValue(ButtonForegroundProperty, value);
    72. }
    73. public static readonly DependencyProperty ButtonForegroundProperty = DependencyProperty.Register(nameof(ButtonForeground), typeof(Brush), typeof(NumericUpDown), new PropertyMetadata(Brushes.Black));
    74. public Brush ButtonMouseOverForeground
    75. {
    76. get => (Brush)GetValue(ButtonMouseOverForegroundProperty);
    77. set => SetValue(ButtonMouseOverForegroundProperty, value);
    78. }
    79. public static readonly DependencyProperty ButtonMouseOverForegroundProperty = DependencyProperty.Register(nameof(ButtonMouseOverForeground), typeof(Brush), typeof(NumericUpDown),new PropertyMetadata(Brushes.Yellow));
    80. public Brush ButtonBackground
    81. {
    82. get => (Brush)GetValue(ButtonBackgroundProperty);
    83. set => SetValue(ButtonBackgroundProperty, value);
    84. }
    85. public static readonly DependencyProperty ButtonBackgroundProperty = DependencyProperty.Register(nameof(ButtonBackground), typeof(Brush), typeof(NumericUpDown), new PropertyMetadata(Brushes.Transparent));
    86. public Brush ButtonMouseOverBackground
    87. {
    88. get => (Brush)GetValue(ButtonMouseOverBackgroundProperty);
    89. set => SetValue(ButtonMouseOverBackgroundProperty, value);
    90. }
    91. public static readonly DependencyProperty ButtonMouseOverBackgroundProperty = DependencyProperty.Register(nameof(ButtonMouseOverBackground), typeof(Brush), typeof(NumericUpDown));
    92. public ICommand IncreaseValueCommand
    93. {
    94. get => (ICommand)GetValue(IncreaseValueCommandProperty);
    95. private set => SetValue(IncreaseValueCommandProperty, value);
    96. }
    97. public static readonly DependencyProperty IncreaseValueCommandProperty;
    98. public ICommand DecreaseValueCommand
    99. {
    100. get => (ICommand)GetValue(DecreaseValueCommandProperty);
    101. private set => SetValue(DecreaseValueCommandProperty, value);
    102. }
    103. public static readonly DependencyProperty DecreaseValueCommandProperty;
    104. static NumericUpDown()
    105. {
    106. DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(typeof(NumericUpDown)));
    107. // Property intialization
    108. StepProperty = DependencyProperty.Register(nameof(StepProperty), typeof(decimal), typeof(NumericUpDown),
    109. new FrameworkPropertyMetadata(default(decimal), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    110. ValueProperty = DependencyProperty.Register(nameof(Value), typeof(decimal), typeof(NumericUpDown),
    111. new FrameworkPropertyMetadata(default(decimal), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    112. MinValueProperty = DependencyProperty.Register(nameof(MinValue), typeof(decimal), typeof(NumericUpDown),
    113. new FrameworkPropertyMetadata(default(decimal), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    114. MaxValueProperty = DependencyProperty.Register(nameof(MaxValue), typeof(decimal), typeof(NumericUpDown),
    115. new FrameworkPropertyMetadata(default(decimal), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    116. // Command initialization
    117. IncreaseValueCommandProperty = DependencyProperty.Register(nameof(IncreaseValueCommand), typeof(ICommand), typeof(NumericUpDown));
    118. DecreaseValueCommandProperty = DependencyProperty.Register(nameof(DecreaseValueCommand), typeof(ICommand), typeof(NumericUpDown));
    119. }
    120. [System.Diagnostics.DebuggerStepThrough]
    121. public NumericUpDown()
    122. {
    123. Value = 0;
    124. MinValue = 0;
    125. MaxValue = 100;
    126. Step = 1M;
    127. Padding = new Thickness(1);
    128. IncreaseValueCommand = new ParameterlessCommand(() =>
    129. {
    130. Value += Step;
    131. },
    132. (o) =>
    133. {
    134. return Value + Step <= MaxValue;
    135. });
    136. DecreaseValueCommand = new ParameterlessCommand(() =>
    137. {
    138. Value -= Step;
    139. },
    140. (o) => {
    141. return Value - Step >= MinValue;
    142. });
    143. }
    144. }
    145. }



    StringToDecimalValueConverter

    C#-Quellcode

    1. using System;
    2. using System.Globalization;
    3. using System.Windows.Data;
    4. namespace CustomControls.Converter
    5. {
    6. internal class StringToDecimalValueConverter : IValueConverter
    7. {
    8. public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    9. {
    10. if (decimal.TryParse(value.ToString(), out decimal val))
    11. return val;
    12. return null;
    13. }
    14. public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    15. {
    16. throw new NotImplementedException();
    17. }
    18. }
    19. }



    ParameterlessCommand

    C#-Quellcode

    1. using System;
    2. using System.Diagnostics;
    3. using System.Windows.Input;
    4. namespace CustomControls.Commands
    5. {
    6. internal class ParameterlessCommand : ICommand
    7. {
    8. private readonly Action action;
    9. private readonly Func<object, bool> canExecute;
    10. public ParameterlessCommand(Action action) : this(action, null) { }
    11. public ParameterlessCommand(Action action, Func<object, bool> canExecute)
    12. {
    13. this.action = action;
    14. this.canExecute = canExecute;
    15. }
    16. public event EventHandler CanExecuteChanged
    17. {
    18. add => CommandManager.RequerySuggested += value;
    19. remove => CommandManager.RequerySuggested -= value;
    20. }
    21. [DebuggerStepThrough]
    22. public bool CanExecute(object parameter)
    23. {
    24. if (canExecute == null)
    25. return true;
    26. return canExecute(parameter);
    27. }
    28. public void Execute(object parameter) => action();
    29. }
    30. }



    NumericUpDown.xaml

    XML-Quellcode

    1. <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    2. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    3. xmlns:local="clr-namespace:CustomControls"
    4. xmlns:converter="clr-namespace:CustomControls.Converter">
    5. <converter:StringToDecimalValueConverter x:Key="StringToDecimalConverter" />
    6. <Style TargetType="{x:Type local:NumericUpDown}">
    7. <Setter Property="Padding" Value="1"/>
    8. <Setter Property="Template">
    9. <Setter.Value>
    10. <ControlTemplate TargetType="{x:Type local:NumericUpDown}">
    11. <ControlTemplate.Resources>
    12. <Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
    13. <Setter Property="Background" Value="{Binding ButtonBackground, RelativeSource={RelativeSource TemplatedParent}}"/>
    14. <Setter Property="BorderThickness" Value="0"/>
    15. <Setter Property="VerticalAlignment" Value="Stretch"/>
    16. <Setter Property="HorizontalAlignment" Value="Stretch"/>
    17. <Setter Property="Template">
    18. <Setter.Value>
    19. <ControlTemplate TargetType="{x:Type Button}">
    20. <Grid Background="{TemplateBinding Background}">
    21. <Border Padding="4 2">
    22. <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    23. </Border>
    24. </Grid>
    25. </ControlTemplate>
    26. </Setter.Value>
    27. </Setter>
    28. <Style.Triggers>
    29. <Trigger Property="IsMouseOver" Value="True">
    30. <Setter Property="Background" Value="{Binding ButtonMouseOverBackground, RelativeSource={RelativeSource TemplatedParent}}" />
    31. </Trigger>
    32. </Style.Triggers>
    33. </Style>
    34. <Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
    35. <Setter Property="Padding" Value="0"/>
    36. <Setter Property="BorderThickness" Value="0"/>
    37. <Setter Property="VerticalAlignment" Value="Stretch"/>
    38. <Setter Property="HorizontalAlignment" Value="Stretch"/>
    39. <Setter Property="VerticalContentAlignment" Value="Center"/>
    40. </Style>
    41. <Style TargetType="{x:Type Path}">
    42. <Setter Property="Stroke" Value="Transparent" />
    43. <Setter Property="StrokeThickness" Value="0" />
    44. <Setter Property="UseLayoutRounding" Value="True" />
    45. <Style.Triggers>
    46. <!-- Trigger order is required, if you need to change the order you have to specify multi data trigger -->
    47. <DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type Button}}}" Value="True">
    48. <Setter Property="Stroke" Value="{Binding ButtonForeground, RelativeSource={RelativeSource TemplatedParent}}" />
    49. <Setter Property="Fill" Value="{Binding ButtonForeground, RelativeSource={RelativeSource TemplatedParent}}"/>
    50. </DataTrigger>
    51. <DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType={x:Type Button}}}" Value="True">
    52. <Setter Property="Stroke" Value="{Binding ButtonMouseOverForeground, RelativeSource={RelativeSource TemplatedParent}}" />
    53. <Setter Property="Fill" Value="{Binding ButtonMouseOverForeground, RelativeSource={RelativeSource TemplatedParent}}"/>
    54. </DataTrigger>
    55. <DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type Button}}}" Value="False">
    56. <Setter Property="Stroke" Value="Gray" />
    57. <Setter Property="Fill" Value="Gray"/>
    58. </DataTrigger>
    59. </Style.Triggers>
    60. </Style>
    61. </ControlTemplate.Resources>
    62. <Border Background="{TemplateBinding Background}"
    63. BorderBrush="{TemplateBinding BorderBrush}"
    64. BorderThickness="{TemplateBinding BorderThickness}"
    65. Height="{TemplateBinding Height}"
    66. Width="{TemplateBinding Width}"
    67. Padding="{TemplateBinding Padding}">
    68. <Grid>
    69. <Grid.ColumnDefinitions>
    70. <ColumnDefinition Width="*"/>
    71. <ColumnDefinition Width=".05*" MinWidth="20"/>
    72. </Grid.ColumnDefinitions>
    73. <Grid.RowDefinitions>
    74. <RowDefinition Height="*"/>
    75. <RowDefinition Height="*"/>
    76. </Grid.RowDefinitions>
    77. <!-- Input TextBox -->
    78. <TextBox Grid.RowSpan="2"
    79. Text="{TemplateBinding Value, Converter={StaticResource StringToDecimalConverter}}"
    80. FontSize="{TemplateBinding FontSize}"
    81. HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
    82. VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
    83. <!-- NumericUp Button -->
    84. <Button Grid.Column="1" Grid.Row="0"
    85. x:Name="ButtonUp"
    86. Command="{TemplateBinding IncreaseValueCommand}">
    87. <Path Data="M201.4 137.4c12.5-12.5 32.8-12.5 45.3 0l160 160c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L224 205.3 86.6 342.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l160-160z" Stretch="Fill" />
    88. </Button>
    89. <!-- NumericDown Button -->
    90. <Button Grid.Column="1" Grid.Row="1"
    91. x:Name="ButtonDown"
    92. Command="{TemplateBinding DecreaseValueCommand}">
    93. <Path Data="M201.4 342.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 274.7 86.6 137.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z" Stretch="Fill"/>
    94. </Button>
    95. </Grid>
    96. </Border>
    97. </ControlTemplate>
    98. </Setter.Value>
    99. </Setter>
    100. </Style>
    101. </ResourceDictionary>



    XML-Quellcode

    1. <Window x:Class="Foo"
    2. xmlns:customControls="clr-namespace:CustomControls;assembly=CustomControls"
    3. d:DataContext="{d:DesignInstance Type=local:MainViewModel}">
    4. <customControls:NumericUpDown Value="{Binding NumericUpDownValue}"
    5. MinValue="-1" MaxValue="4"
    6. Width="40" Height="20" BorderThickness=".5" BorderBrush="Black"
    7. ButtonMouseOverBackground="Red"
    8. ButtonMouseOverForeground="White"/>

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Fakiz“ ()

    Fakiz schrieb:

    Geht beides, wenn das Projekt größer ist würde ich mit MVVM-Pattern arbeiten bei kleineren Projekten kann der MVVM Aufwand den Nutzen schnell übersteigen....
    Dem täte ich sehr widersprechen.
    Jedes Projekt - egal wie klein - profitiert vom MVVM-Pattern.
    Ich rate dringend davon ab, es überhaupt zu versuchen, die in der Wpf abgelegten Anti-Pattern wiederzubeleben.
    Merke: Einüben von Anti-Pattern erweitert nicht deine Skills, sondern macht dich letztendlich nur dümmer.
    Ich hatte mich zu früh gefreut.

    Es funktioniert zwar, dass wenn ich meinen Up-Button drücke, meine Werte hoch gezählt werden, aber irgendwann
    läuft der Wert runter, und kurz darauf bleibt das Programm irgendwo (außerhalb meines Codes, aber unterbrechbar) hängen.
    Das ist sehr suspekt. Ich habe noch keine Ahnung, aber ich vermute, das ist irgend ein Timing-Problem
    beim AddHandler-setzen und wieder removen, zwischen meinem Code und WPF.