Can I apply a style to a XAML element based on what parent element it has? - xaml

What is the correct way to apply styles on elements with a condition on their parent element's type, i. e. only if they are children of certain other elements?
In my case, I want to apply some exact button width and height, but only if those buttons are direct children of a stackpanel. Additionally, a second style should be applied to images within those buttons (glyphs).
How do I define a button style that only affects buttons on a stackpanel, but not those buttons placed directly on a grid?
Is it possible to add additional conditions such as only stackpanels with orientation="horizontal"?
Can I define "tree conditions" like only images on buttons on [horizontal] stackpanels?
As 90% of all buttons in my application are those on the stackpanels, so far I've applied the style to all buttons and images and overrode it where necessary. But this isn't the best solution, is it?
Preferably, the solution would deal with all the conditions in the style definition, so I won't have to explicitly assign that style to every single one of my stackpanels.

<StackPanel>
<StackPanel.Resources>
<Style x:Key="Rectangle1" TargetType="Rectangle">
<Setter Property="Stroke" Value="Black" />
<Setter Property="Fill" Value="White" />
</Style>
</StackPanel.Resources>
<UniformGrid Columns="10">
<UniformGrid.Resources>
<Style TargetType="Rectangle" BasedOn="{StaticResource Rectangle1}">
<Setter Property="Fill" Value="Red" />
</Style>
</UniformGrid.Resources>
</UniformGrid>
</StackPanel>

Related

How to inherit from, or override elements of, the Default style for a WinUI 3 control?

I am trying to learn how to use Styles most effectively in WinUI 3 (from WindowsAppSDK 1.1.1) but I'm having difficulty getting simple inheritance to work.
Consider the NavigationViewItem class. I'd like to modify the default style to bind the FontSize and Height properties. The following works in my Page XAML:
<NavigationViewItem x:Uid="Shell_05" helpers:NavigationHelper.NavigateTo="ViewModels._05CreditViewModel"
FontSize="{Binding ViewModel.RootShellFontSize, ElementName=shellPage}"
Height="{Binding ViewModel.CurrentMenuItemHeight, ElementName=shellPage}">
<NavigationViewItem.Icon>
<BitmapIcon UriSource="\Images\credit.png"/>
</NavigationViewItem.Icon>
</NavigationViewItem>
But adding the two properties to a page resource does not (although the FontSize property works in each of the following. It's the Height that doesn't):
<Page.Resources>
<Style TargetType="NavigationViewItem" >
<Setter Property="FontSize" Value="{Binding ViewModel.RootShellFontSize, ElementName=shellPage}" />
<Setter Property="Height" Value="{Binding ViewModel.CurrentMenuItemHeight, ElementName=shellPage}" />
</Style>
</Page.Resources>
Neither does adding the style to a resource dictionary and merging. I've read over what I can find about inheriting styles and the BasedOn="" extension is an explicit way to derive from an existing style in WinUI versions prior to 2.6 (I think). Apparently, WinUI 3 does not require BasedOn. In any case, simply specifying TargetType="NavigationViewItem" doesn't work, but nor does
<Style TargetType="controls:NavigationViewItem" BasedOn="DefaultNavigationViewItemStyle">
The source code for v1.1.1 of the SDK declares a default style for the NavigationViewItem in generic.xaml, but there is no definition for DefaultNavigationViewItemStyle.
I also cannot derive from the default style using
<Style TargetType="controls:NavigationViewItem" BasedOn="{StaticResource {x:Type NavigationViewItem}}">
because x:Type is undefined.
I can do all of the bindings I want in code but I assume it's both clearer and more efficient to do it in XAML.
How do I inherit, derive from, or override a portion of the default style for a WinUI 3 control (not a custom control) in a desktop application, please?
Thanks for any help. Pointers to good XAML for WinUI 3 documentation (or books and articles) would also be greatly appreciated.
In your case the height is most probably not working since page.resources get compiled before object initialization and the height of the CurrentMenuItemHeight is 0. To solve it just set the mode to one way as such
{Binding ViewModel.CurrentMenuItemHeight,Mode=OneWay , ElementName=shellPage}
When you wish to use BasedOn, just say BasedOn={ThemeResource styleName}.
Just make sure the style is actually defined in Generic.xaml file which u can find in "C:\Users\AdminName.nuget\packages\microsoft.windowsappsdk\1.1.1\lib\uap10.0\Microsoft.UI\Themes"
So your final page.resources should be as such:
<Page.Resources>
<Style TargetType="NavigationViewItem" >
<Setter Property="FontSize" Value="{Binding ViewModel.RootShellFontSize, ElementName=shellPage}" />
<Setter Property="Height" Value="{Binding ViewModel.CurrentMenuItemHeight, Mode=OneWay, ElementName=shellPage}" />
</Style>
</Page.Resources>
But it would be much better to use x:Bind instead of Binding. You can view this page to learn more about it https://learn.microsoft.com/en-US/windows/uwp/data-binding/data-binding-in-depth

XAML reuse specific UI elements

With Xamarin, I have a small UI element which acts as a content divider:
<BoxView StyleClass="contentDivider"
HeightRequest="2"
WidthRequest="1000"
Margin="3, 0"/>
Since I use this a number of times I wanted to be able to have the code written down once, and reuse that code - just like a class with its instance (DRY). It's most likely me being a blind bat and not being able to find how it's done. So, how can I reuse XAML elements?
You can do this with ContentViews (https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/controls/layouts#contentview), which probably works better for larger reuse cases (using more XAML in the ContentView).
Yet, for such a small single element example as yours, you could really just consider using a global style (https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/styles/xaml/application) which its looks like you already have with StyleClass="contentDivider", as long as you only want to override properties on a single element (like your BoxView).
Just add HeightRequest, WidthRequest and Margin to your style and your done.
<Style x:Key="contentDivider" TargetType="BoxView">
<Setter Property="HeightRequest" Value="20" />
<Setter Property="WidthRequest" Value="20" />
<Setter Property="Margin" Value="0,99,0,0" />
... etc
</Style>

provide a style for textblock in the gridview

I have a gridview and there're many gridviewitems in it, every gridviewitem contains some textblock, could I specify a style for all of the textblock at once? Not specify the style one by one?
You should be able to leverage the object resources and name it implicitly. Something like;
<GridView>
<GridView.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Red"/>
<!-- Etc, etc, etc -->
</Style>
</GridView.Resources>
</GridView>

Giving a specific style to DataGrid element (implicitly)

I'm trying implicitly apply a style for DataGrid and TextBlocks.
For TextBlock's ForeGround I need White color.
For DataGrid's rows I need Black Color.
Beside this I need White again for DataGrid's header columns.
When I globally apply an implicit style for on MainPage by
<UserControl>
<UserControl.Resorces>
<Style targetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
</Style>
</UserControl.Resorces>
</UserControl>
Making TextBlock's Foreground White operation is done! But beside this all of elements
in DataGrid (By default content elements are textblock's I think) turn to White color.
It doesn't look good White on white as you guess :)
So how can I particularly specify DataGrid's elements Foreground to black?
I can do it by using same technic shown below ,but this is an expensive operation for each DataGrid. As a con more I want DataGrid's HeaderColumns white again.This operation make them all black.
Is there an explicit way such as we do in css styles?
Here is what I tried to achieve this goal by Control template. But no chance because of being DataGrid's ContentControl is dynamic.
<DataGrid>
<DataGrid.Resources>
<Style targetType="TextBlock">
<Setter Property="Foreground" Value="Black"/>
</Style>
<DataGrid.Resources>
In fact we use Telerik's RadGridView but I give a sdk's DataGrid example to make question more global.
<Style TargetType="sdk:DataGrid">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="RowDetailsTemplate" Value="{StaticResource DataTemplate1}"/>
<Setter Property="Template" Value="{StaticResource ControlTemplate1}"/>
</Style>
<ControlTemplate x:Key="ControlTemplate1" TargetType="sdk:DataGrid">
<Grid/>
</ControlTemplate>
<DataTemplate x:Key="DataTemplate1">
<Grid/>
</DataTemplate>
Thanks in advance!
If it were me I would pull out the full control templates and style them accordingly instead of trying to just do adhoc setter changes to override bits of the original template. In Expression Blend right click, choose "Edit Template -> Edit A Copy" and break out the templates for your rows etc and apply those implicitly with StaticResource instead.

DXGrid change color of grid row DevExpress WPF

I have entity with property IsRemoved. When it is become true grid row should be Gray.
To do this I am using this code:
<dxg:TableView.RowStyle>
<Style TargetType="{x:Type dxg:GridRowContent}">
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.IsRemoved, Mode=OneWay}" Value="True">
<Setter Property="Background" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</dxg:TableView.RowStyle>
</dxg:TableView>
But It will run only when grid shows first time. I want to change color when value is changing. Property implement INotifyPropertyChange Event.
Note: this answer is legacy (see my other answer).
This answer is for DevExpress versions prior to v14.1, or DevExpress versions v14.1 and after with
UseLightweightTemplates="None".
You need to have an initial setter for the property you want to change. This is due to the order in which WPF uses styles.
Include this line after your style tag:
<Setter Property="Background" Value="Black" />
Full Example:
<dxg:TableView.RowStyle>
<Style TargetType="{x:Type dxg:GridRowContent}">
<Setter Property="Background" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.IsRemoved, Mode=OneWay}" Value="True">
<Setter Property="Background" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</dxg:TableView.RowStyle>
Starting with v14.1 of DevExpress, they introduced Optimized Mode which uses Lightweight Templates. This makes everything faster, but requires a change to how the styles and DataTriggers are specified.
Lightweight Templates are controlled by a the attached property UseLightweightTemplates="Row", which is on by default. It can be switched to None for backwards compatibility.
Here is a working MVVM example of how to color a row if the IsDirty property is set for any grid row.
<dxg:GridControl x:Name="MyGridControl"
ItemsSource ="{Binding MyViewModelList}"
SelectionMode="None"
VerticalAlignment="Stretch">
<dxg:GridControl.Resources>
<SolidColorBrush x:Key="GridRowIsDirty" Color="#FF602D2D" />
</dxg:GridControl.Resources>
<dxg:GridControl.View>
<dxg:TableView UseLightweightTemplates="Row" >
<dxg:TableView.RowStyle>
<Style TargetType="dxg:RowControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Row.IsDirty}" Value="True">
<Setter Property="Background" Value="{StaticResource GridRowIsDirty}" />
</DataTrigger>
</Style.Triggers>
</Style>
</dxg:TableView.RowStyle>
</dxg:TableView>
</dxg:GridControl.View>
<dxg:GridControl.Columns>
<dxg:GridColumn x:Name="Included" FieldName="Included"/>
<dxg:GridColumn x:Name="ColumnB" Header="Column B" FieldName="ColumnB" ReadOnly="True"/>
<dxg:GridColumn x:Name="ColumnC" Header="Column C" FieldName="ColumnC" ReadOnly="True"/>ReadOnly="True"/>
</dxg:GridControl.Columns>
</dxg:GridControl>
In the ViewModel behind this grid:
public ObservableCollection<MyViewModel> MyViewModelList { get; set; }
Every row in the grid points to a class of type MyViewModel, which contains a custom IsDirty flag which we can set on demand:
public bool IsDirty
{
get { return _isDirty; }
set
{
_isDirty = value;
OnPropertyChanged();
}
}
Appendix A: Additional Links
See DevExpress: How to disable focused/selected row colors.
See DevExpress: Optimized Mode.
See DevExpress: DXGrid: DataTrigger does not seem to work with UseLightweightTemplates="All".
See DevExpress: Binding to the RowData.Row property is not updated when changing a specific data row property.
See DevExpress: DxGrid: Grid does not update until I scroll the row on off and one the screen.
Appendix B: Other solutions
This also works most of the time, but it will not work if the source of the event is via a context menu, so it is not recommended:
<DataTrigger Binding="{Binding DataContext.IsDirty}" Value="True">
<Setter Property="Background" Value="{StaticResource GridRowIsDirty}" />
</DataTrigger>
Appendix C: AllowLiveDataShaping
If the trigger is not firing, try switching on AllowLiveDataShaping="True" in <GridControl>. However, try to avoid this as it (theoretically) has an impact on the speed of large, complex grids (it has no discernable impact on most grids of a reasonable size).
Appendix D: If all else fails, use a custom ControlTemplate
With the introduction of "UseLightweightTemplates", DevExpress has been focusing on speed. However, the techniques used for speed involve switching off bindings that might slow things down.
This means that if we change something in a DxGrid cell, the value in the ViewModel does not change until the user shifts to the next cell or row. This means that the ViewModel lags behind what is actually in the grid.
To fix this, the only solution that I could find was to bypass DevExpress's templates entirely, and use my own. This means that the DxGrid has no choice but to display a custom template which updates the ViewModel instantaneously as soon as the user edits it, which means that the row color changes immediately:
<dxg:GridControl Grid.Row="3" x:Name="TrsGridControl"
ItemsSource ="{Binding MyObservableCollection}"
VerticalAlignment="Stretch"
AllowLiveDataShaping ="True">
<dxg:GridControl.Resources>
<converter:TestConverter x:Key="TestConverter" />
<ControlTemplate x:Key="DisplayedOnTicketTrs">
<dxe:CheckEdit x:Name="DisplayedOnTicketCheckEdit" HorizontalAlignment="Center" IsChecked="{Binding RowData.Row.DisplayedOnTicket, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
</dxg:GridControl.Resources>
<dxg:GridControl.View>
<dxg:TableView UseLightweightTemplates="All"/>
</dxg:GridControl.View>
<dxg:GridControl.Columns>
<dxg:GridColumn x:Name="DisplayedOnTicketTrs" DisplayTemplate="{StaticResource DisplayedOnTicketTrs}" Header="Displayed On Ticket?" HeaderToolTip="Displayed On Ticket?" AllowEditing="False"/>
Header ="Displayed On Ticket?"/>
<dxg:GridColumn x:Name="ColumnA" Header="ColumnA" FieldName="ColumnA" ReadOnly="True"/>
<dxg:GridColumn x:Name="ColumnB" Header="ColumnB" FieldName="ColumnB" ReadOnly="True"/>
</dxg:GridControl.Columns>
</dxg:GridControl>
After I made this change, everything started to work:
When the checkbox is clicked, the background color changes instantly (if we add the trigger to change the background color, above).
Editing the DxGrid changes the ViewModel instantaneously.
Changing the ViewModel updates the DxGrid instantaneously.
If a ContextMenu updates the ViewModel, then everything just works.
you should write just "Row" instead of "DataContext"