Eto Forms Styling

Hey,

I’m diving head-first into Eto.Forms and have a few questions about styling of various UI elements. The aim is to create a tabbed interface with a few collapsible element groups to enhance user interaction.

image

  1. How can I change the color and thickness of all borders? Can’t find it anywhere in TabControl, TabPage, or Panel. Ideally, I’d like to remove the dividing line between the TabControl elements and the panel whose visibility they toggle.

  2. How can I change the font of the Expander.Header element? It’s possible on a GroupBox as illustrated above.

  3. Changing Expander.BackgroundColor and Expander.Header.BackgroundColor doesn’t seem to have any effect and forces the default grey color no matter what the above are set to.

Is there a style inheritance hierarchy, which one needs to be aware of to control these elements’ appearance?
Could you shed some light at it @curtisw?

3 Likes

Bumping this thread with a more general question: how can we style Eto forms with Wpf?

It’s straightforward to create style definitions using exposed properties like so:

 Eto.Style.Add<GroupBox>("myBox", box =>
 {
    box.Font = new Font("Segoe UI", 9, FontStyle.Bold);
    box.Padding = new Padding(10);
 });

But trying to access Windows-specific Wpf style controls doesn’t do anything. There is no error on compile, but the following line has zero effect:

Eto.Style.Add<GroupBoxHandler>("myBox", h => { h.Control.BorderThickness = new System.Windows.Thickness(4); });

Any ideas @dale, @curtisw?

1 Like

Hey @mrhe,

Sorry for the slow response. You can certainly access all of the visual styling at least on Windows (where Eto uses WPF), since Eto.Forms is simply an abstraction of WPF on Windows, and AppKit on macOS.

Controls on AppKit/macOS are generally less flexible on what you can change, and with WPF there is a lot of flexibility but it can be much more difficult to do as you sometimes have to use XAML styles.

Hopefully these answers help:

  1. To change the color and thickness of borders of a TabControl, you’ll have to create a WPF style for its TabItem and apply that to that WPF control.

  2. The Expander.Header is a Control, so you can usually just do:

    myExpander.Header = new Label { Font = SystemFonts.Bold(12), Text = "My Text" }
    

    Unfortunately, we have customized the Expander specifically for Rhino to change its style, and it doesn’t allow you to change the font directly in the same way. I’ve created RH-67888 to look into that. In the meantime, this might work better when working in a panel:

    myExpander.Header = new Panel { Content = new Label { Font = .., Text = "My Text" }
    

    or

    myExpander.Header = "My Text";
    if (myExpander.Header is Label label)
    {
      label.Font = ...
    }
    
  3. It should be possible to set the background color of an Expander, but I think what you might be running into is that Rhino itself applies styles and themes to pretty much all Eto controls when hosted in a Rhino Panel (vs. using a Form or Dialog). We have some internal helper methods to turn this off in certain cases, but we have not exposed this API yet. I’ve logged RH-67885 to make it public for 3rd party devs. It should hopefully land in 7.17 soon.

  4. As for the GroupBox border thickness, that is working for me. Are you sure your GroupBoxHandler style is being added before you have myGroupBox.Style = "myBox";? Perhaps it needs to be moved, preferably in a static constructor or when your plugin is loaded.

If you want full control of your visual style, I’d recommend also looking into using a Drawable in which you can paint any of the elements yourself in its Paint event. Rhino’s custom Expander for example uses that, along with many other UI elements.

1 Like

Hi,

I added the below code to the Displacement dialog

m_holder.Styles.Add<Rhino.UI.Controls.CollapsibleSectionContainerHandler>(null, c =>
  {
    Eto.Forms.Label title = c.Header as Eto.Forms.Label;
    if (title != null)
      title.Font = new Eto.Drawing.Font("Arial", 24, Eto.Drawing.FontStyle.Italic, Eto.Drawing.FontDecoration.Underline);
  });

Here is the result

Maybe this solution might work.

1 Like

Thanks @curtisw, @maxsoder,

Could you please provide an example on how to apply this style from Eto?


I’ve found out that direct attempts to change the font of Expander.Header fail, but using a Label style works:

Expander additionalControlsExpander = new Expander
{
    Style = "expanderStyle",
    Expanded = true,
    Content = CreateGroupBoxControl("", Settings()),
    Header = new Label
    {
        Text = "Additional Controls",
        Style = "expanderLabelStyle",
    }
};

Styles.Add<Label>("expanderLabelStyle", expanderlabel =>
{
     expanderlabel.Font = new Font("Segoe UI", 9, FontStyle.Bold);
});

This works as expected:
image



Thanks for exposing the Background color controls in Rhinocommon! It’s quite confusing to work with form styling without understanding Rhino’s inheritance hierarchy and having handy overrides to customize things. For now, we managed to unify background colors of all controls by a global style definition:

Styles.Add<Control>(null, defaultstyle => { defaultstyle.BackgroundColor = Color.FromArgb(255, 255, 255); });

I added it to the OnLoad override of my plugin.

protected override LoadReturnCode OnLoad(ref string errorMessage)
{
    Eto.Style.Add<Eto.Wpf.Forms.Controls.GroupBoxHandler>(null, h => { h.Control.BorderThickness = new System.Windows.Thickness(4); });
    Panels.RegisterPanel(this, typeof(Views.TerrainPanel), "Terrain", Properties.Resources.TerrainIcon);
}

No success. This only compiles up to Rhinocommon 7.13.21348.13001, later versions throw the following errors:

Severity Code Description
Error CS0311 The type ‘Eto.Wpf.Forms.Controls.GroupBoxHandler’ cannot be used as type parameter ‘TWidget’ in the generic type or method ‘Style.Add(string, StyleWidgetHandler)’. There is no implicit reference conversion from ‘Eto.Wpf.Forms.Controls.GroupBoxHandler’ to ‘Eto.Widget’.
Error CS0012 The type ‘WidgetHandler<,>’ is defined in an assembly that is not referenced. You must add a reference to assembly ‘Eto, Version=2.6.0.0, Culture=neutral, PublicKeyToken=null’.

I’m referencing a NuGet Eto.Platform.Wpf 2.6.0 package. This automatically adds it’s own version of Eto.dll on compile. I then delete it in post-build events like so:

cd $(TargetDir)
del Eto.dll

Could it be messing things up?

1 Like

Absolutely, I’ll try to cook something up for you.

Great, that looks good. We were a little overzealous on applying background colours to all controls, as it really is only needed for the main panel. Either way though, the new APIs should help get you to a blank slate.

While you can do this, you must not include any of the associated assemblies, such as Eto.dll and Eto.Wpf.dll. An easier way is to use the RhinoWindows nuget package, which includes Eto.Wpf.

Hope this helps!

1 Like

This was the missing piece of the puzzle! Switching to the RhinoWindows nuget package made the handler approach work. Now, we can override the appearance of individual elements like so:

Eto.Style.Add<Eto.Wpf.Forms.Controls.GroupBoxHandler>(null, h => { 
    h.Control.BorderThickness = new System.Windows.Thickness(2);
    h.Control.BorderBrush = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(255, 0, 255));
});

image

@curtisw, how would you approach developing for Mac, though? The whole idea behind using Eto was to have a cross-platform UI. I was hoping to use these style overrides only on Windows and fall back to defaults on OSX. This made sense while referencing RhinoCommon. Doesn’t switching to RhinoWindows invalidate this approach?

Great! Glad it’s working.

If you want to include windows-specific functionality you can do it a number of ways. First would be to compile your plugin specifically for Windows and for Mac. Yak/PackageManager packages support having platform specific packages.

The second approach would be to move your platform-specific code to a separate assembly and only load/execute that code when running on that platform. This is basically what Eto does, which allows you to create your own extended versions of controls like the GroupBox, or just roll your own thing to execute the styles.

Makes perfect sense, thanks again @curtisw!

Would be awesome if you could still provide an example on how to customize the Wpf templates and load them from Eto. Your support is much appreciated!

1 Like

Big thanks Curtis!

hi mariusz, looks like you kind of solved this yourself in your terrain plugin. I wonder when you will share some of the epic Eto UI magic you’ve done for that? :wink:

You mean this?

Coming soon™ :wink:

[EDIT] I just scrolled up to the top to see where it all started a little bit more than a year ago:
image

It’s been a fun journey ever since and @Wiley and I have learned heaps in the process. Let me know if the community would be interested in us sharing our experience with UI development. We could cook something up maybe.

15 Likes

Errr daaah, yes, drool…

I just started looking into Eto and will be on the same journey. Would make sense to compile good examples.

Edit: 3 likes in 1hr shows community is ripe.

1 Like

FYI, I started a new thread to continue this discussion: