What I want to do
I've been exploring XAML Resource Dictionaries recently. They are very powerful, but in order to cut down (even further) on the changes that would need to be made to accommodate any modifications, I'd like to use some basic arithmetic operations to change the HeightRequest property of an Entry.
I'm already making good use of OnPlatform and OnIdiom for different aspects, like FontSize.
For the iOS Platform, I'd like to make the HeightRequest of an entry 20+(FontSize). The FontSize is already set using OnIdiom (it's slightly increased for tablets).
In a perfect world, the core thing which I'm trying to do might look something like
<Setter Property="HeightRequest" Value="{DynamicResource StandardFontSize}+10">
What "works"
I have a working solution if I use a combination of OnIdiom and OnPlatform.
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamarinDesigner.App"
xmlns:local="clr-namespace:XamarinDesigner"
>
<Application.Resources>
<ResourceDictionary>
<OnIdiom x:Key="StandardFontSize" x:TypeArguments="x:Double" Tablet="22" Phone="18"/>
<Style x:Key="MyEntry" TargetType="Entry">
<Setter Property="FontSize" Value="{DynamicResource StandardFontSize}"/>
<Setter Property="HeightRequest">
<Setter.Value>
<OnIdiom x:TypeArguments="x:Double">
<OnIdiom.Phone>
<OnPlatform x:TypeArguments="x:Double" iOS="30"/>
</OnIdiom.Phone>
<OnIdiom.Tablet>
<OnPlatform x:TypeArguments="x:Double" iOS="40"/>
</OnIdiom.Tablet>
</OnIdiom>
</Setter.Value>
</Setter>
<Setter Property="VerticalOptions" Value="Center"/>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
With this 'solution' - I need to set the value explicitly and do the calculations myself. While this works, I'd like to be able to perform a basic arithmetic operation to find the value of FontSize, and add some number to it.
What I've tried
In another attempt I've made, I've found a converter and tried to adapt it to my use case. While there is no intellisense or build/compile errors, the app crashes immediately after opening. The .cs file for ArithmeticConverter can be found in the link above.
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamarinDesigner.App"
xmlns:local="clr-namespace:XamarinDesigner"
>
<Application.Resources>
<local:ArithmeticConverter x:Key="AScript"/>
<ResourceDictionary>
<OnIdiom x:Key="StandardFontSize" x:TypeArguments="x:Double" Tablet="22" Phone="18"/>
<Style x:Key="MyEntry" TargetType="Entry">
<Setter Property="FontSize" Value="{DynamicResource StandardFontSize}"/>
<Setter Property="HeightRequest" Value="{Binding Converter={StaticResource AScript},ConverterParameter=Int32.Parse(20+{DynamicResource StandardFontSize}}"/>
<Setter Property="VerticalOptions" Value="Center"/>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
I don't fully understand the use of converters, and {Binding} inside of a value in App.xaml is also something that is new to me. Looking at the example provided with the converter, I think I'm close to being correct, and may just need a push in the right direction?
Is it possible to do this basic sort of arithmetic function in the App.xaml alone(or with the use of a converter)? I'm hoping to contain as much as I can to this file.
Other solutions I've found in my search have mentioned the use of a viewmodel, but this is a 'global' change I want to apply to every entry per platform/idiom, so I can't see how that adaption might work.
Thanks for your time!
One of the reason your app is crashing is because Converter is outside the ResourceDictionary.
Solution 1
Binding should be used only when there is a BindingContext assigned, hence you need to assign it in cs file.
App.cs:
public App()
{
InitializeComponent();
BindingContext = new { EntryHeightRequest = 10 };
MainPage = ...
}
App.xaml:
<ResourceDictionary>
<local:ArithmeticConverter x:Key="AScript"/>
<OnIdiom x:Key="StandardFontSize" x:TypeArguments="x:Double" Tablet="22" Phone="18"/>
<Style x:Key="MyEntry" TargetType="Entry">
<Setter Property="FontSize" Value="{DynamicResource StandardFontSize}" />
<Setter Property="HeightRequest" Value="{Binding EntryHeightRequest, Converter={StaticResource AScript},ConverterParameter="{StaticResource StandardFontSize}"/>
<Setter Property="VerticalOptions" Value="Center"/>
</Style>
</ResourceDictionary>
ArithmeticConverter.cs:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is int constant && parameter is OnIdiom<double> dynamicSize)
return constant + dynamicSize.GetValue();
return -1;
}
OnIdiomExtension:
public static T GetValue<T>(this OnIdiom<T> idiom)
{
switch(Device.Idiom)
{
case TargetIdiom.Phone:
return idiom.Phone;
case TargetIdiom.Desktop:
return idiom.Desktop;
case TargetIdiom.Tablet:
return idiom.Tablet;
case TargetIdiom.TV:
return idiom.TV;
case TargetIdiom.Watch:
return idiom.Watch;
default:
throw new NotSupportedException();
}
}
Beware: When I tried, BindingContext is passed to ResourceDictionary(but this post contradicts it, may be they changed?)
Solution 2
Similar to Solution 1 but instead of setting BindingContext you can use OnIdiom on HeightRequest with default value.
<Setter Property="HeightRequest" Value="{OnIdiom Default=10, Converter={StaticResource AScript}, ConverterParameter={StaticResource StandardFontSize}}" />
I have the following:
<sys:String x:Key="NoDeviceAlert" xml:space="preserve">Your device is currently disabled.
Please ensure it is turned on and connected.</sys:String>
However it doesn't work. The TextBlock:
<TextBlock Text="{DynamicResource NoDeviceAlert}" Style="{DynamicResource msgTextStyle}" HorizontalAlignment="Center" />
keeps both sentences on a single line, but removes the decimal values. I have also tried hex values as well as /r/n.
How can this not work?
UPDATE
<Style x:Key="msgTextStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="#FFC8DBE7" />
<Setter Property="FontFamily" Value="/Project;component/Utilities/Resources/Fonts/frutiger.ttf#Frutiger Linotype" />
<Setter Property="FontSize" Value="20" />
<Setter Property="TextAlignment" Value="Left" />
</Style>
ResourceDictionary sys namespace:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
The application is for Windows desktop and using .net 4.5.
The attribute xml:space="preserve" affects only working of XML parser. So line breaks, tabs and spaces will stay preserved.
Instead of typing explicit newline chars just press enter:
<sys:String x:Key="NoDeviceAlert" xml:space="preserve">Your device is currently disabled.
Please ensure it is turned on and connected.</sys:String>
You can try use
&#x 0a;
like a line break (without this space inside).
It should be work.
I want to have a logout icon in the appbar.
I was sure it should be in the common styles, but discovered that unfortunately it's not.
So, how can I do it?
I've found this site, where I found the icon I wanted to the logout button, and copied the xaml which gave me the path. Next I added this code to the common:
<Style x:Key="LogoutAppBarButtonStyle" TargetType="ButtonBase" BasedOn="{StaticResource AppBarButtonStyle}">
<Setter Property="AutomationProperties.AutomationId" Value="LogoutAppBarButton" />
<Setter Property="AutomationProperties.Name" Value="Logout" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Viewbox RenderTransformOrigin="0.47,0.47">
<Viewbox.RenderTransform>
<TransformGroup>
<CompositeTransform Rotation="0" ScaleX="0.551720260135184" ScaleY="0.551720260135184" />
</TransformGroup>
</Viewbox.RenderTransform>
<Path Stretch="Uniform"
Fill="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Data="F1 M 0,71.4297C -0.0207825,54.2669 7.09511,41.2825 13.974,33.0403C 20.8893,24.7292 27.6055,20.7252 28.2083,20.3522C 32.6992,17.6946 38.4714,19.2199 41.0976,23.7583C 43.7188,28.2812 42.2317,34.0878 37.7787,36.7583L 37.7604,36.7707L 37.7044,36.8053L 37.2148,37.1308L 35.1185,38.6797C 33.3203,40.1106 30.849,42.3333 28.414,45.2734C 23.5221,51.2292 18.8593,59.6705 18.8385,71.427C 18.8424,83.2799 23.5678,93.9374 31.2579,101.724C 38.9648,109.497 49.5065,114.279 61.2304,114.281C 72.9544,114.279 83.4961,109.497 91.2019,101.724C 98.8932,93.9374 103.619,83.2799 103.622,71.427C 103.602,60.0305 99.2304,51.7707 94.5065,45.8346C 90.0091,40.207 85.1979,37.0722 84.7188,36.7799L 84.7031,36.7721L 84.6888,36.7642L 84.6784,36.7597C 80.2304,34.0865 78.7435,28.2799 81.362,23.7571C 83.9909,19.2187 89.7618,17.6933 94.2514,20.3509C 94.8568,20.7226 101.573,24.7265 108.488,33.0384C 115.371,41.2825 122.483,54.2655 122.464,71.427C 122.457,105.611 95.0443,133.322 61.2317,133.333C 27.4205,133.322 0.00785828,105.611 0,71.4297 Z M 51.8125,66.668L 51.8125,38.0944L 51.8125,9.52411C 51.8125,4.26556 56.03,-3.05176e-005 61.233,-3.05176e-005C 66.4323,-3.05176e-005 70.6497,4.26556 70.6497,9.52411L 70.6497,38.0944L 70.6497,66.668L 70.6524,66.668C 70.6524,71.9218 66.4323,76.1901 61.233,76.1901C 56.03,76.1901 51.8125,71.9218 51.8125,66.668 Z " />
</Viewbox>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
It is important to pay attention to the Path's Fill property, it can't be white color, because when we press on it and hold it, the default appbar icons become white, so the color should be changed to black, and therefore, we use this code which get's the foreground color from the parent - AppBarButtonStyle, which already has this logic on appbar icon pressed. This is the code:
Fill="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=TemplatedParent}}"
One more thing, I have used some renders because the image was a little bit crooked, so I have fixed it by rendering it, maybe not the best way, but now it works perfectly !
The last step was to add this to all the pages I wanted to use the login appbar icon:
<Button Style="{StaticResource LogoutAppBarButtonStyle}" Click="Logout_Click" />
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.
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"