question

EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 asked EmonHaque-1485 edited

How to create Custom H/VScrollBar and set those as default for entire Application?

After experimenting on FrameworkElement, Panel, UIElement and animations for a month I've decided to give my old RentManager App a new look and some animations with all those I've learnt and without any xaml. So far the basic navigation, transition animations and some custom controls are done and here's how it looks:

92604-new.gif

Up until now I haven't use any Binding to have those effects and this way, creating three abstract subclass of FrameworlElement and Panel, is actually way more easier than doing these in xaml. Anyway, the problem, right now, is ScrollBar and I've never created any custom ScrollBar before. In the previous version of my App I'd these:

 <ControlTemplate x:Key="ThumbTemplate" TargetType="{x:Type Thumb}">
     <Rectangle x:Name="rectangle" Fill="LightGray" Opacity=".5" RadiusX="10" RadiusY="{Binding RadiusX, RelativeSource={RelativeSource Self}}"/>
     <ControlTemplate.Triggers>
         <Trigger Property="IsMouseOver" Value="true">
             <Setter Property="Opacity" TargetName="rectangle" Value="1"/>
             <Setter Property="Fill" TargetName="rectangle" Value="LightSlateGray"/>
         </Trigger>
         <Trigger Property="IsDragging" Value="true">
             <Setter Property="Opacity" TargetName="rectangle" Value="1"/>
             <Setter Property="Fill" TargetName="rectangle" Value="Gray"/>
         </Trigger>
     </ControlTemplate.Triggers>
 </ControlTemplate>

 <Style TargetType="{x:Type ScrollBar}">
     <Setter Property="Template">
         <Setter.Value>
             <ControlTemplate TargetType="{x:Type ScrollBar}">
                 <Grid>
                     <Grid.RowDefinitions>
                         <RowDefinition Height="{Binding Source={x:Static con:Constants.ScrollBarThickness}}"/>
                         <RowDefinition Height="*"/>
                         <RowDefinition Height="{Binding Source={x:Static con:Constants.ScrollBarThickness}}"/>
                     </Grid.RowDefinitions>

                     <cc:ScrollButton Grid.Row="0" Command="ScrollBar.LineUpCommand" Geometry="{Binding Source={x:Static con:Constants.ScrollUpIcon}}"/>
                     <Track Grid.Row="1" x:Name="PART_Track" IsDirectionReversed="true" >
                         <Track.DecreaseRepeatButton>
                             <cc:ScrollButton Command="ScrollBar.PageUpCommand"/>
                         </Track.DecreaseRepeatButton>
                         <Track.IncreaseRepeatButton>
                             <cc:ScrollButton Command="ScrollBar.PageDownCommand"/>
                         </Track.IncreaseRepeatButton>
                         <Track.Thumb>
                             <Thumb Template="{StaticResource ThumbTemplate}"/>
                         </Track.Thumb>
                     </Track>
                     <cc:ScrollButton Grid.Row="2" Command="ScrollBar.LineDownCommand" Geometry="{Binding Source={x:Static con:Constants.ScrollDownIcon}}"/>
                 </Grid>
             </ControlTemplate>
         </Setter.Value>
     </Setter>
     <Style.Triggers>
         <Trigger Property="Orientation" Value="Horizontal">
             <Setter Property="Template">
                 <Setter.Value>
                     <ControlTemplate TargetType="{x:Type ScrollBar}">
                         <Grid>
                             <Grid.ColumnDefinitions>
                                 <ColumnDefinition Width="{Binding Source={x:Static con:Constants.ScrollBarThickness}}"/>
                                 <ColumnDefinition Width="*"/>
                                 <ColumnDefinition Width="{Binding Source={x:Static con:Constants.ScrollBarThickness}}"/>
                             </Grid.ColumnDefinitions>

                             <cc:ScrollButton Grid.Column="0" Command="ScrollBar.LineLeftCommand" Geometry="{Binding Source={x:Static con:Constants.ScrollLeftIcon}}"/>
                             <Track Grid.Column="1" x:Name="PART_Track" IsDirectionReversed="False" >
                                 <Track.DecreaseRepeatButton>
                                     <cc:ScrollButton Command="ScrollBar.PageLeftCommand"/>
                                 </Track.DecreaseRepeatButton>
                                 <Track.IncreaseRepeatButton>
                                     <cc:ScrollButton Command="ScrollBar.PageRightCommand"/>
                                 </Track.IncreaseRepeatButton>
                                 <Track.Thumb>
                                     <Thumb Template="{StaticResource ThumbTemplate}"/>
                                 </Track.Thumb>
                             </Track>
                             <cc:ScrollButton Grid.Column="2" Command="ScrollBar.LineRightCommand" Geometry="{Binding Source={x:Static con:Constants.ScrollRightIcon}}"/>
                         </Grid>
                     </ControlTemplate>
                 </Setter.Value>
             </Setter>
         </Trigger>
     </Style.Triggers>
 </Style>

in App.xaml and it looks like this:

92605-old.gif

For the ScrollButton, I'd these in Themes.xaml:

 <Style BasedOn="{StaticResource {x:Type RepeatButton}}" TargetType="{x:Type local:ScrollButton}">
     <Setter Property="Template">
         <Setter.Value>
             <ControlTemplate TargetType="{x:Type local:ScrollButton}">
                 <Grid Background="Transparent">
                     <Path x:Name="icon"
                           Stretch="Uniform"
                           Fill="Gray"
                           Data="{Binding Geometry, RelativeSource={RelativeSource TemplatedParent}}"/>
                 </Grid>
                 <ControlTemplate.Triggers>
                     <Trigger Property="IsMouseOver" Value="true">
                         <Setter TargetName="icon" Property="Fill" Value="Black"/>
                     </Trigger>
                     <Trigger Property="IsPressed" Value="true">
                         <Setter TargetName="icon" Property="Fill" Value="LightGray"/>
                     </Trigger>
                 </ControlTemplate.Triggers>
             </ControlTemplate>
         </Setter.Value>
     </Setter>
 </Style>

and in .cs file these:

 public class ScrollButton : RepeatButton
 {
     static ScrollButton()
     {
         DefaultStyleKeyProperty.OverrideMetadata(typeof(ScrollButton), new FrameworkPropertyMetadata(typeof(ScrollButton)));
     }
     public string Geometry
     {
         get { return (string)GetValue(GeometryProperty); }
         set { SetValue(GeometryProperty, value); }
     }
     public static readonly DependencyProperty GeometryProperty =
         DependencyProperty.Register("Geometry", typeof(string), typeof(ScrollButton), new PropertyMetadata(null));
 }

Now I don't have App.xaml anymore, I've App.cs instead to instantiate my custom window, RootWindow. I also wanted to get rid of the AssemblyInfo.cs BUT I get some error if I remove that file.

windows-wpf
new.gif (945.0 KiB)
old.gif (126.3 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 answered EmonHaque-1485 edited

Now everything appears, functional and is perfect:

94124-test.gif

For Thumb and Track I've these three subclasses:

 class ScrollThumb : ControlTemplate
 {
     public ScrollThumb() {
         TargetType = typeof(Thumb);
         var rect = new FrameworkElementFactory(typeof(Rectangle)) { Name = "rect" };
         rect.SetValue(Rectangle.RadiusXProperty, 10d);
         rect.SetValue(Rectangle.RadiusYProperty, 10d);
         rect.SetValue(Rectangle.OpacityProperty, 0.5d);
         rect.SetValue(Rectangle.FillProperty, Brushes.LightGray);
         Triggers.Add(new Trigger() {
             Property = Thumb.IsMouseOverProperty,
             Value = true,
             Setters = {
                 new Setter() {
                     Property = Rectangle.FillProperty,
                     Value = Brushes.Gray,
                     TargetName = "rect"
                 }
             }
         });
         Triggers.Add(new Trigger() {
             Property = Thumb.IsDraggingProperty,
             Value = true,
             Setters = {
                 new Setter() {
                     Property = Rectangle.FillProperty,
                     Value = Brushes.Gray,
                     TargetName = "rect"
                 }
             }
         });
         VisualTree = rect;
     }
 }
 class VTrack : Track
 {
     public VTrack() {
         IncreaseRepeatButton = new ScrollButton() { Command = ScrollBar.PageUpCommand };
         DecreaseRepeatButton = new ScrollButton() { Command = ScrollBar.PageDownCommand };
         Thumb = new Thumb() { Template = new ScrollThumb() };
     }
 }
 class HTrack : Track
 {
     public HTrack() {
         IncreaseRepeatButton = new ScrollButton() { Command = ScrollBar.PageLeftCommand };
         DecreaseRepeatButton = new ScrollButton() { Command = ScrollBar.PageRightCommand };
         Thumb = new Thumb() { Template = new ScrollThumb() };
     }
 }

ScrollButton now has a static constructor:

 static ScrollButton() {
     DefaultStyleKeyProperty.OverrideMetadata(typeof(ScrollButton), new FrameworkPropertyMetadata(typeof(ScrollButton)));
 }

and Brushes.CornflowerBlue for IsMouseOverProperty Trigger. Some changes in ScrollBar Templates:

 class VScrollTemplate : ControlTemplate
 {
     public VScrollTemplate() {
         TargetType = typeof(ScrollBar);
         var vGrid = new FrameworkElementFactory(typeof(Grid));
         var row1 = new FrameworkElementFactory(typeof(RowDefinition));
         var row2 = new FrameworkElementFactory(typeof(RowDefinition));
         var row3 = new FrameworkElementFactory(typeof(RowDefinition));
         var upButton = new FrameworkElementFactory(typeof(ScrollButton));
         var track = new FrameworkElementFactory(typeof(VTrack)) { Name = "PART_Track" };
         var downButton = new FrameworkElementFactory(typeof(ScrollButton));
         row1.SetValue(RowDefinition.HeightProperty, new GridLength(1, GridUnitType.Auto));
         row3.SetValue(RowDefinition.HeightProperty, new GridLength(1, GridUnitType.Auto));
         upButton.SetValue(ScrollButton.IconProperty, Icons.ScrollUp);
         upButton.SetValue(RepeatButton.CommandProperty, ScrollBar.LineUpCommand);
         track.SetValue(Grid.RowProperty, 1);
         track.SetValue(Track.IsDirectionReversedProperty, true);
            
         downButton.SetValue(Grid.RowProperty, 2);
         downButton.SetValue(ScrollButton.IconProperty, Icons.ScrollDown);
         downButton.SetValue(RepeatButton.CommandProperty, ScrollBar.LineDownCommand);

         vGrid.AppendChild(row1);
         vGrid.AppendChild(row2);
         vGrid.AppendChild(row3);
         vGrid.AppendChild(upButton);
         vGrid.AppendChild(track);
         vGrid.AppendChild(downButton);
         VisualTree = vGrid;
     }
 }

 class HScrollTemplate : ControlTemplate
 {
     public HScrollTemplate() {
         TargetType = typeof(ScrollBar);
         var hGrid = new FrameworkElementFactory(typeof(Grid));
         var col1 = new FrameworkElementFactory(typeof(ColumnDefinition));
         var col2 = new FrameworkElementFactory(typeof(ColumnDefinition));
         var col3 = new FrameworkElementFactory(typeof(ColumnDefinition));
         var leftButton = new FrameworkElementFactory(typeof(ScrollButton));
         var track = new FrameworkElementFactory(typeof(HTrack)) { Name = "PART_Track" };
         var rightButton = new FrameworkElementFactory(typeof(ScrollButton));
         col1.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto));
         col3.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto));
         leftButton.SetValue(ScrollButton.IconProperty, Icons.ScrollLeft);
         leftButton.SetValue(RepeatButton.CommandProperty, ScrollBar.LineLeftCommand);
         track.SetValue(Grid.ColumnProperty, 1);
         track.SetValue(Track.IsDirectionReversedProperty, true);
         rightButton.SetValue(Grid.ColumnProperty, 2);
         rightButton.SetValue(ScrollButton.IconProperty, Icons.ScrollRight);
         rightButton.SetValue(RepeatButton.CommandProperty, ScrollBar.LineRightCommand);
            
         hGrid.AppendChild(col1);
         hGrid.AppendChild(col2);
         hGrid.AppendChild(col3);
         hGrid.AppendChild(leftButton);
         hGrid.AppendChild(track);
         hGrid.AppendChild(rightButton);
         VisualTree = hGrid;
     }
 }

in App.cs, I have added this function:

 static void setScrollbarStyle() {
     var scrollBarStyle = new Style() {
         TargetType = typeof(ScrollBar),
         Setters = {
             new Setter() {
                 Property = ScrollBar.TemplateProperty,
                 Value = new VScrollTemplate()
             }
         },
         Triggers = {
             new Trigger() {
                 Property = ScrollBar.OrientationProperty,
                 Value = Orientation.Horizontal,
                 Setters = {
                     new Setter() {
                         Property = ScrollBar.TemplateProperty,
                         Value = new HScrollTemplate()
                     }
                 }
             }
         }
     };
     Control.StyleProperty.OverrideMetadata(typeof(ScrollBar), new FrameworkPropertyMetadata { DefaultValue = scrollBarStyle });
     Current.Resources.Add(SystemParameters.VerticalScrollBarWidthKey, Constants.ScrollBarThickness);
     Current.Resources.Add(SystemParameters.HorizontalScrollBarHeightKey, Constants.ScrollBarThickness);
 }

and called it after var app = new App();. Constants.ScrollBarThickness is set to 12d.


test.gif (218.3 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

DaisyTian-1203 avatar image
1 Vote"
DaisyTian-1203 answered EmonHaque-1485 edited

You can put your ScrollBar Style to a ResourceDictionary, then add it to your App.cs file like below:

  [STAThread]
         static void Main()
         {         
             Application app = new Application();
             app.Resources.MergedDictionaries.Add (Application.LoadComponent(new Uri("/MyNameSpace;Component/Styles/MyStyleDC.xaml", UriKind.Relative)) as ResourceDictionary);
             app.StartupUri = new Uri("MainWindow.xaml", UriKind.Relative);
             app.Run();
         }

AssemblyInfo.cs represents an assembly, which is a reusable, versionable, and self-describing building block of a common language runtime application.If you delete it, your assembly will be compiled with no information. In the Details tab of the file properties you will see no name, no description, version 0.0.0.0, etc. For the more information, please refer to Assembly Class and the answer.


If the response is helpful, please click "Accept Answer" and upvote it.
Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

· 3
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@DaisyTian-MSFT, no! The plan is to do everything without any xaml.

0 Votes 0 ·

@EmonHaque-1485
Do you want to create Style and ControlTemplate in the cs code with any xaml code? How about using below code:

 private ControlTemplate GetControlTemplate()
         {
             string template = "<ControlTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' " +
                 "xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>" +
                "Your Xaml code";
             var tem = XamlReader.Parse(template);
             return tem as ControlTemplate;
         }
0 Votes 0 ·

@DaisyTian-MSFT, yes, so far, without any xaml, WPF experience is great and far more easier than doing those in xaml except the DataTemplate part shown here in the answer section.

0 Votes 0 ·
EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 answered EmonHaque-1485 edited

I've tried with these:

94039-test.gif

ScrollButton and Thumb BUT doesn't work! Here's the Thumb:

 class ScrollThumb : Thumb
 {
     public ScrollThumb() {
         var rect = new FrameworkElementFactory(typeof(Rectangle)) { Name = "rect" };
         rect.SetValue(Rectangle.RadiusXProperty, 10d);
         rect.SetValue(Rectangle.RadiusYProperty, 10d);
         rect.SetValue(Rectangle.OpacityProperty, 0.5d);
         rect.SetValue(Rectangle.FillProperty, Brushes.LightGray);
         Template = new ControlTemplate() {
             VisualTree = rect,
             Triggers = {
                 new Trigger() {
                     Property = IsMouseOverProperty,
                     Value = true,
                     Setters = {
                         new Setter() {
                             Property = Rectangle.FillProperty,
                             Value = Brushes.Blue,
                             TargetName = "rect"
                         }
                     },
                 }
             }
         };
     }
 }

the ScrollButton:

 class ScrollButton : RepeatButton
 {
     public string Icon {
         get { return (string)GetValue(IconProperty); }
         set { SetValue(IconProperty, value); }
     }

     public static readonly DependencyProperty IconProperty =
         DependencyProperty.Register("Icon", typeof(string), typeof(ScrollButton), new PropertyMetadata() {
             DefaultValue = null,
             PropertyChangedCallback = (s, e) => (s as ScrollButton).setTemplate()
         });

     void setTemplate() {
         var border = new FrameworkElementFactory(typeof(Border));
         var path = new FrameworkElementFactory(typeof(Path)) { Name = "path" };
         path.SetValue(Path.StretchProperty, Stretch.Uniform);
         path.SetValue(Path.FillProperty, Brushes.Gray);
         path.SetValue(Path.DataProperty, Geometry.Parse(Icon));
         border.SetValue(Border.BackgroundProperty, Brushes.Transparent);
         border.AppendChild(path);
         Template = new ControlTemplate(typeof(RepeatButton)) {
             VisualTree = border,
             Triggers = {
                 new Trigger() {
                     Property = IsMouseOverProperty,
                     Value = true,
                     Setters = {
                         new Setter() {
                             Property = Path.FillProperty,
                             Value = Brushes.Blue,
                             TargetName = "path"
                         }
                     },
                 }
             }
         };
     }
 }

and the ControlTemplate of H/VScrollbar:

 class VScrollTemplate : ControlTemplate
 {
     public VScrollTemplate() {
         TargetType = typeof(ScrollBar);
         var vGrid = new FrameworkElementFactory(typeof(Grid));
         var row1 = new FrameworkElementFactory(typeof(RowDefinition));
         var row2 = new FrameworkElementFactory(typeof(RowDefinition));
         var row3 = new FrameworkElementFactory(typeof(RowDefinition));
         var upButton = new FrameworkElementFactory(typeof(ScrollButton));
         var downButton = new FrameworkElementFactory(typeof(ScrollButton));
         var track = new FrameworkElementFactory(typeof(Track)) { Name = "PART_Track" };

         row1.SetValue(Grid.HeightProperty, 12d);
         row3.SetValue(Grid.HeightProperty, 12d);
         track.SetValue(Grid.RowProperty, 1);
         downButton.SetValue(Grid.RowProperty, 2);
         upButton.SetValue(ScrollButton.IconProperty, Icons.ScrollUp);
         upButton.SetValue(RepeatButton.CommandProperty, ScrollBar.LineUpCommand);
         track.SetValue(Track.IsDirectionReversedProperty, true);
         track.SetValue(Thumb.TemplateProperty, new ScrollThumb().Template);
         downButton.SetValue(ScrollButton.IconProperty, Icons.ScrollDown);
         downButton.SetValue(RepeatButton.CommandProperty, ScrollBar.LineDownCommand);

         vGrid.AppendChild(row1);
         vGrid.AppendChild(row2);
         vGrid.AppendChild(row3);
         vGrid.AppendChild(upButton);
         vGrid.AppendChild(track);
         vGrid.AppendChild(downButton);
         VisualTree = vGrid;
     }
 }

 class HScrollTemplate : ControlTemplate
 {
     public HScrollTemplate() {
         TargetType = typeof(ScrollBar);
         var hGrid = new FrameworkElementFactory(typeof(Grid));
         var col1 = new FrameworkElementFactory(typeof(ColumnDefinition));
         var col2 = new FrameworkElementFactory(typeof(ColumnDefinition));
         var col3 = new FrameworkElementFactory(typeof(ColumnDefinition));
         var leftButton = new FrameworkElementFactory(typeof(ScrollButton));
         var rightButton = new FrameworkElementFactory(typeof(ScrollButton));
         var track = new FrameworkElementFactory(typeof(Track)) { Name = "PART_Track" };

         col1.SetValue(Grid.WidthProperty, 12d);
         col3.SetValue(Grid.WidthProperty, 12d);
         track.SetValue(Grid.ColumnProperty, 1);
         rightButton.SetValue(Grid.ColumnProperty, 2);
         leftButton.SetValue(ScrollButton.IconProperty, Icons.ScrollLeft);
         leftButton.SetValue(RepeatButton.CommandProperty, ScrollBar.LineLeftCommand);
         track.SetValue(Track.IsDirectionReversedProperty, true);
         track.SetValue(Thumb.TemplateProperty, new ScrollThumb().Template);
         rightButton.SetValue(ScrollButton.IconProperty, Icons.ScrollRight);
         rightButton.SetValue(RepeatButton.CommandProperty, ScrollBar.LineRightCommand);

         hGrid.AppendChild(col1);
         hGrid.AppendChild(col2);
         hGrid.AppendChild(col3);
         hGrid.AppendChild(leftButton);
         hGrid.AppendChild(track);
         hGrid.AppendChild(rightButton);
         VisualTree = hGrid;
     }
 }

in the App.cs, I've added these:

 class App : Application
 {
     public static MainVM mainVM;
     [STAThread]
     static void Main() {
         var app = new App();
         var scrollBarStyle = new Style() {
             TargetType = typeof(ScrollBar),
             Setters = {
                 new Setter() {
                     Property = ScrollBar.TemplateProperty,
                     Value = new VScrollTemplate()
                 }
             },
             Triggers = {
                 new Trigger() {
                     Property = ScrollBar.OrientationProperty,
                     Value = Orientation.Horizontal,
                     Setters = {
                         new Setter() {
                             Property = ScrollBar.TemplateProperty,
                             Value = new HScrollTemplate()
                         }
                     }
                 }
             }
         };
         //app.Resources.Add("scroll", scrollBarStyle);
         Application.Current.Resources.Add("scroll", scrollBarStyle);
         mainVM = new MainVM();
         app.MainWindow = new RootWindow() { 
             Content = new RootPanel() {
                 Children = {
                 new AddView(),
                 new EditView(),
                 new ReportView()
             }
             }
         };
         app.MainWindow.Show();
         app.Run();
     }
 }

BUT these don't change the appearance of ScrollBar! I couldn't set the Track.IncreaseRepeatButton and Track.DecreaseRepeatButton in H/VScrollTemplate ! Are those DependencyProperty?


test.gif (44.4 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.