Dynamic resource not updating ImageButton style in .NET MAUI - xaml

I defined two ContentPage level styles to be bound dynamically to an ImageButton. When the ImageButton click event is called, it is supposed to switch the ImageButton style, but this is not happening.
Below is the ContentPage content with the styles and the ImageButton definition:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SampleMobile.SamplePage"
Title="">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="defaultStyle" TargetType="ImageButton">
<Setter Property="BorderColor" Value="Grey"/>
<Setter Property="BorderWidth" Value="2" />
</Style>
<Style x:Key="selectedStyle" TargetType="ImageButton">
<Setter Property="BorderColor" Value="Blue"/>
<Setter Property="BorderWidth" Value="5" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<Grid RowDefinitions="50, 100, 5, 100, 5, 50, 100, 100, 100" ColumnDefinitions="*, *, *, *"
Padding="25, 35, 25, 35" ColumnSpacing="5" RadioButtonGroup.GroupName="mobileNetworks">
<Label Grid.Row="0" Grid.ColumnSpan="4"
Text="Select Network"
VerticalOptions="Center"
HorizontalOptions="Center" />
<ImageButton Source="first.png" Grid.Row="1" Grid.Column="0" HeightRequest="50" WidthRequest="50" CornerRadius="10" Clicked="SelectImage" Style="{DynamicResource imageButtonStyle}"/>
<ImageButton Source="second.png" Grid.Row="1" Grid.Column="1" BorderWidth="2" HeightRequest="50" WidthRequest="50" BorderColor="Grey" CornerRadius="10"/>
<ImageButton Source="third.png" Grid.Row="1" Grid.Column="2" BorderWidth="2" HeightRequest="50" WidthRequest="50" BorderColor="Grey" CornerRadius="10"/>
<ImageButton Source="fourth.png" Grid.Row="1" Grid.Column="3" BorderWidth="2" HeightRequest="50" WidthRequest="50" BorderColor="Grey" CornerRadius="10"/>
</Grid>
</ContentPage>
Below is the code behind file where the first style is set, and the second style is set inside the click event handler:
public partial class SamplePage : ContentPage
{
public SamplePage()
{
InitializeComponent();
Resources["imageButtonStyle"] = Resources["defaultStyle"];
}
private void SelectImage(object sender, EventArgs e)
{
Resources["imageButtonStyle"] = Resources["selectedStyle"];
}
}
I'm still trying to find what is wrong, and why it is not working as expected.

You can use this.
First give the ImageButton a name like this x:Name="Image1"
I added 2 Buttons to change it and go back.
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="defaultStyle" TargetType="ImageButton">
<Setter Property="BorderColor" Value="Gray"/>
<Setter Property="BorderWidth" Value="2" />
</Style>
<Style x:Key="selectedStyle" TargetType="ImageButton">
<Setter Property="BorderColor" Value="Blue"/>
<Setter Property="BorderWidth" Value="5" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<Frame BackgroundColor="#2196F3" Padding="24" CornerRadius="0">
<Label Text="Welcome to Xamarin.Forms!" HorizontalTextAlignment="Center" TextColor="White" FontSize="36"/>
</Frame>
<ImageButton x:Name="Image1" Source="balzwart.png" Style="{DynamicResource defaultStyle}" />
<Button Text="Change" Clicked="Button_Clicked" />
<Button Text="Back" Clicked="Button_Clicked_1" />
</StackLayout>
and for the Button Click
private void Button_Clicked(object sender, EventArgs e)
{
Image1.Style = (Style)Resources["selectedStyle"];
}
private void Button_Clicked_1(object sender, EventArgs e)
{
Image1.Style = (Style)Resources["defaultStyle"];
}
Find it here https://github.com/borisoprit/DynamicSO

Instead of manipulating the styles at runtime, which is only a semi-good idea and should be done differently anyway, I recommend you do something like below instead using Visual States and only one Style for the ImageButton:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SelectedImageSample.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="ImageButtonStyle" TargetType="ImageButton">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BorderColor" Value="Blue" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<Grid RowDefinitions="50, 100, 5, 100, 5, 50, 100, 100, 100" ColumnDefinitions="*, *, *, *"
Padding="25, 35, 25, 35" ColumnSpacing="5" RadioButtonGroup.GroupName="mobileNetworks">
<Label Grid.Row="0" Grid.ColumnSpan="4"
Text="Select Network"
VerticalOptions="Center"
HorizontalOptions="Center" />
<ImageButton Source="first.png" Grid.Row="1" Grid.Column="0" BorderWidth="2" HeightRequest="50" WidthRequest="50" CornerRadius="10" Clicked="SelectImage" Style="{StaticResource ImageButtonStyle}"/>
<ImageButton Source="second.png" Grid.Row="1" Grid.Column="1" BorderWidth="2" HeightRequest="50" WidthRequest="50" CornerRadius="10" Clicked="SelectImage" Style="{StaticResource ImageButtonStyle}"/>
<ImageButton Source="third.png" Grid.Row="1" Grid.Column="2" BorderWidth="2" HeightRequest="50" WidthRequest="50" CornerRadius="10" Clicked="SelectImage" Style="{StaticResource ImageButtonStyle}"/>
<ImageButton Source="fourth.png" Grid.Row="1" Grid.Column="3" BorderWidth="2" HeightRequest="50" WidthRequest="50" CornerRadius="10" Clicked="SelectImage" Style="{StaticResource ImageButtonStyle}"/>
</Grid>
</ContentPage>
In your Code Behind, you then can use the event handler to set the Visual State as follows:
private void SelectImage(object sender, EventArgs e)
{
if (sender is ImageButton imageButton)
{
VisualStateManager.GoToState(imageButton, "Selected");
}
}
This is just the minimal demonstration of how it can be done without manipulating a style in the resources. If you want to unselect the ImageButton again, you'll need to implement some logic and an Unselected Visual State.
Update 1
It might be useful to store some kind of state on the button by adding an IsSelected property (e.g. via inheritance). Then the Visual State can be updated accordingly.
Update 2
If you don't want to extend ImageButton, you could also use an Attached Property to store the state for the button.

Related

How do I write xaml to bind (in MAUI) to a field other than the one specified in ItemsSource of the CollectionView?

I have this (probably naïve question):
I have this xaml:
<CollectionView
ItemsSource="{Binding Receipts}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Receipt">
<Grid Padding="1, 1, 1, 1">
<Frame Style="{StaticResource CardView}">
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:ER_Expense_Receipts_View_VM}}, Path=GoToReceiptDetailsCommand}"
CommandParameter="{Binding .}" />
</Frame.GestureRecognizers>
<Grid RowDefinitions="Auto, *" ColumnDefinitions="310, 20" RowSpacing="10">
<Image
x:Name="imgReceipt"
Aspect="AspectFit"
Margin="0"
Grid.ColumnSpan="2"
Source="{Binding ContentsThumbnail, Mode=OneWay, Converter={StaticResource ByteArrayToImageSourceConverter}}"
WidthRequest="300" />
<Label
Grid.Row="1"
FontSize="12"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
VerticalOptions="Start"
VerticalTextAlignment="Center"
HeightRequest="30"
Text="{Binding Description}">
</Label>
<ImageButton
x:Name="cmdDelete"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:ER_Expense_Receipts_View_VM}}, Path=DeleteReceiptCommand}"
CommandParameter="{Binding ID}"
Grid.Row="1"
Grid.Column="1"
Source="delete2.svg"
HeightRequest="20"
WidthRequest="40">
<ImageButton.Triggers>
<DataTrigger TargetType="ImageButton" Binding="{Binding ExpenseReport.Status}" Value="{x:Static res:AppRes.StatusSent}">
<Setter Property="IsEnabled" Value="False" />
<Setter Property="TextColor" Value="Gray" />
</DataTrigger>
</ImageButton.Triggers>
</ImageButton>
</Grid>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Receipts is a collection of Receipt objects in my "ER_Expense_Receipts_View_VM" model:
[ObservableProperty]
private Receipt receipt;
public ObservableCollection<Receipt> Receipts { get; } = new();
Also in my model I have this property
[ObservableProperty]
private Expense expense;
What I need to do here is to bind the "cmdDelete" ImageButton's property IsEnabled to the Status field of the ExpenseReport object in the model. See below. Alas, I get the message "Member not found in data context 'Receipt'".
So, please, what I am doing wrong here ? What's the correct syntax to bind that IsEnabled property to some field in the model other than from the Receipt objects ?
Thank you very much,
Alex

.Net maui radio button checked changed event not firing with control template

I am using radio button control with customised radio button selection colors in .Net Maui.But when changing the selection , it doesn't fire CheckedChanged event.
Here is the code snippet,
View
<StackLayout
x:Name="SexRadioGroup"
Grid.Row="10"
Margin="24,12,0,0"
Orientation="Horizontal"
RadioButtonGroup.GroupName="SexRadioGroup"
Spacing="40">
<RadioButton
GroupName="SexRadioGroup"
IsChecked="True"
Value="Male">
<RadioButton.Content>
<StackLayout>
<Label
Margin="30,0,0,0"
Text="Male"
TextColor="{StaticResource FieldNameTextColor}"
VerticalOptions="Start" />
</StackLayout>
</RadioButton.Content>
</RadioButton>
<RadioButton
CheckedChanged="SexRadioButton_CheckedChanged"
GroupName="SexRadioGroup"
Value="Female">
<RadioButton.Content>
<StackLayout>
<Label
Margin="30,0,0,0"
Text="Female"
TextColor="{StaticResource FieldNameTextColor}"
VerticalOptions="Start" />
</StackLayout>
</RadioButton.Content>
</RadioButton>
</StackLayout>
Code behind file
private void SexRadioButton_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
}
Style(RadioButton template)
<ControlTemplate x:Key="RadioButtonTemplate">
<Frame
Padding="0"
BackgroundColor="#F3F2F1"
BorderColor="#F3F2F1"
HasShadow="False"
HeightRequest="100"
HorizontalOptions="Start"
VerticalOptions="Start"
WidthRequest="100">
<Grid Margin="4" WidthRequest="100">
<Grid
HeightRequest="18"
HorizontalOptions="End"
VerticalOptions="Start"
WidthRequest="18">
<Ellipse
Fill="White"
HeightRequest="16"
HorizontalOptions="Center"
Stroke="Blue"
VerticalOptions="Center"
WidthRequest="16" />
<Ellipse
x:Name="check"
Fill="Blue"
HeightRequest="8"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="8" />
</Grid>
<ContentPresenter />
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroupList>
<VisualStateGroup x:Name="CheckedStates">
<VisualState x:Name="Checked">
<VisualState.Setters>
<Setter Property="BorderColor" Value="#FF3300" />
<Setter TargetName="check" Property="Opacity" Value="1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unchecked">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="#F3F2F1" />
<Setter Property="BorderColor" Value="#F3F2F1" />
<Setter TargetName="check" Property="Opacity" Value="0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</VisualStateManager.VisualStateGroups>
</Frame>
</ControlTemplate>
<Style TargetType="RadioButton">
<Setter Property="ControlTemplate" Value="{StaticResource RadioButtonTemplate}" />
</Style>
Is there any way to use renderer to change the selection and unselection color of the radio button by renderers?
The cause is the clicked event has been dealt with by the Frame, not the radio button. So you can add the TapGestureRecognizer to the Frame. I use the control template in the App.xaml. Such as:
<ControlTemplate x:Key="RadioButtonTemplate">
<Frame
Padding="0"
BackgroundColor="#F3F2F1"
BorderColor="#F3F2F1"
HasShadow="False"
HeightRequest="100"
HorizontalOptions="Start"
VerticalOptions="Start"
WidthRequest="100">
<Frame.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
</Frame.GestureRecognizers>
And in the App.cs:
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
      {
            Frame frame = sender as Frame;
            RadioButton button = (RadioButton)frame.Parent;
            button.IsChecked = true;
}
And then, the radio button checked changed event will be hit when you click it. (Note: On the windows platform, it can work without the TapGestureRecognizer.)

RadioButton unaligned with UI in XAML

Below image you see the red circle indicators where it's only possible to click and radiobutton works, but I want to scoop them to the correct place(to the left so it's aligned with UI), which I don't know how. I tried TranslationY=-80 but it didn't work.
Styles.xaml:
<ControlTemplate x:Key="RadioButtonTemplate">
<Frame BorderColor="#F3F2F1" CornerRadius="2" BackgroundColor="#F3F2F1" HasShadow="False" HeightRequest="80" WidthRequest="80" HorizontalOptions="Start" VerticalOptions="Start" Padding="0">
<VisualStateManager.VisualStateGroups>
<VisualStateGroupList>
<VisualStateGroup x:Name="CheckedStates">
<VisualState x:Name="Checked">
<VisualState.Setters>
<Setter Property="BorderColor" Value="#FF3300" />
<Setter TargetName="check" Property="Opacity" Value="1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unchecked">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="#F3F2F1" />
<Setter Property="BorderColor" Value="#F3F2F1" />
<Setter TargetName="check" Property="Opacity" Value="0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</VisualStateManager.VisualStateGroups>
<Grid Margin="4" WidthRequest="80">
<Grid WidthRequest="18" HeightRequest="18" HorizontalOptions="End" VerticalOptions="Start">
<Ellipse Stroke="Blue" Fill="White" WidthRequest="16" HeightRequest="16" HorizontalOptions="Center" VerticalOptions="Center" />
<Ellipse x:Name="check" Fill="Blue" WidthRequest="8" HeightRequest="8" HorizontalOptions="Center" VerticalOptions="Center" />
</Grid>
<ContentPresenter />
</Grid>
</Frame>
</ControlTemplate>
<Style TargetType="RadioButton">
<Setter Property="ControlTemplate" Value="{StaticResource RadioButtonTemplate}" />
</Style>
MyPage.xaml:
<StackLayout Spacing="3">
<Label Text="{Binding SelectedTheme, StringFormat='Theme: {0}'}" FontSize="20" />
<Grid ColumnDefinitions="*,*,*" RadioButtonGroup.GroupName="themes" RadioButtonGroup.SelectedValue="{Binding SelectedTheme, Mode=TwoWay}">
<RadioButton Value="{x:Static am:AppTheme.Unspecified}" CheckedChanged="RadioButton_CheckedChanged">
<RadioButton.Content>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Image Scale="0.75" Source="{FontImage FontFamily=FAS, Glyph={x:Static fontawesome:FontAwesomeIcons.Gear}, Color=#323130}" />
<Label FontSize="Small" Text="System" TextColor="#323130" />
</StackLayout>
</RadioButton.Content>
</RadioButton>
<RadioButton Value="{x:Static am:AppTheme.Light}" Grid.Column="1" CheckedChanged="RadioButton_CheckedChanged">
<RadioButton.Content>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Image Scale="0.75" Source="{FontImage FontFamily=FAS, Glyph={x:Static fontawesome:FontAwesomeIcons.Sun}, Color=#323130}" />
<Label FontSize="Small" Text="Light" TextColor="#323130" />
</StackLayout>
</RadioButton.Content>
</RadioButton>
<RadioButton Value="{x:Static am:AppTheme.Dark}" Grid.Column="2" CheckedChanged="RadioButton_CheckedChanged">
<RadioButton.Content>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Image Scale="0.75" Source="{FontImage FontFamily=FAS, Glyph={x:Static fontawesome:FontAwesomeIcons.Moon}, Color=#323130}" />
<Label FontSize="Small" Text="Dark" TextColor="#323130" />
</StackLayout>
</RadioButton.Content>
</RadioButton>
</Grid>
</StackLayout>
It's hard to say it is a potential issue or by design .
To solve the problem/as a workaround , you can add a TapGestureRecognizer on ContentPresenter to enable the RadioButton manually .
Sample code
//xaml
<ContentPresenter >
<ContentPresenter.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
</ContentPresenter.GestureRecognizers>
</ContentPresenter>
//code behind
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
ContentPresenter cp = sender as ContentPresenter;
var button = cp.Content.Parent as RadioButton;
button.IsChecked = true;
}

Xamarin.Forms. Collectionview adds a lot of extra white space after its content

I would like there to be no empty space after the content of the collection view.
I tried to put my button in footer but then i ran into problem that property isenabled = false is not being applied to it in my code behind.
Here is my xaml file:
<ContentPage.Content>
<RefreshView x:DataType="local:QuestionViewModel" Command="{Binding LoadCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
<ScrollView>
<StackLayout>
<views:MyTextView LaTeX="{Binding Formulation}" HorizontalOptions="Center" Margin="15, 10, 15, 0"/>
<!--<RefreshView x:DataType="local:QuestionViewModel" Command="{Binding LoadCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">-->
<CollectionView
ItemsSource="{Binding Answers, Mode=TwoWay}"
SelectedItems="{Binding SelectedAnswers, Mode=TwoWay}"
SelectionMode="Multiple"
x:Name="collectionView"
SelectionChanged="collectionView_SelectionChanged"
>
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Answer">
<StackLayout Padding="10">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal">
<VisualState.Setters>
<Setter TargetName="frame" Property="Frame.BackgroundColor" Value="{Binding AnswerColor}"/>
</VisualState.Setters>
</VisualState>
<VisualState Name="Selected">
<VisualState.Setters>
<Setter TargetName="frame" Property="Frame.BackgroundColor" Value="{DynamicResource MainColor}"/>
<Setter TargetName="label" Property="views:MyTextView.TextColor" Value="White"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Frame CornerRadius="10" HasShadow="True" x:Name="frame" BackgroundColor="{Binding AnswerColor}">
<views:MyTextView x:Name="label" LaTeX="{Binding Content}" />
</Frame>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<!--</RefreshView>-->
<StackLayout>
<Label x:Name="conditionLabel" TextColor="{DynamicResource AnswerColor}"/>
<Button x:Name="checkButton" Text="Проверить ответы" CornerRadius="10" Margin="10"
Command="{Binding CheckAnswersCommand}"/>
</StackLayout>
</StackLayout>
</ScrollView>
</RefreshView>
</ContentPage.Content>
Result:
1 part of screen
2 part of screen
You shouldn't use Listview/Collection View with in Scrollview as described in official documentation.
There is an alternative to listview within Scrollview. Look at following code:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ScrollViewDemos"
x:Class="ScrollViewDemos.Views.ColorListPage"
Title="ScrollView demo">
<ScrollView>
<StackLayout BindableLayout.ItemsSource="{x:Static local:NamedColor.All}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<BoxView Color="{Binding Color}"
HeightRequest="32"
WidthRequest="32"
VerticalOptions="Center" />
<Label Text="{Binding FriendlyName}"
FontSize="24"
VerticalOptions="Center" />
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
</ContentPage>
Follow Documentation for more support:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/scrollview
Unfortunately, this is a known problem, and an open issue.
See that thread for possible work-arounds. And to track any progress on fixing it.
Bottom line: you have to manually specify/calculate height. There are multiple ways to do so, of various difficulty. Thread mentions some. You would need to Google for more details.
One quick-and-dirty solution I like, when acceptable, is to wrap CollectionView in Grid, and then use RowDefinitions to force how much height is given to row containing CollectionView, how much to everything else. Something like:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<CollectionView Grid.Row="0" ...>
...
</CollectionView>
<StackLayout Grid.Row = "1" ...>
... everything that is below the CV ...
</StackLayout>
</Grid>
This would restrict row 0 (which contains CollectionView) to top 1/3 of screen.

Bind properties to styles defined at ResourceDictionary to eliminate code duplication

I have the following XAML code:
<Window x:Class="LinkButton.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LinkButton"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
DataContext="{StaticResource MainWindowVM}">
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="10" />
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="ddk" />
<TextBlock Grid.Row="0" Grid.Column="1" >
<Hyperlink Command="{Binding Link}"
CommandParameter="{Binding}"
Foreground="Blue" >
<Hyperlink.Inlines>
<TextBlock>
<TextBlock.Style>
<Style>
<Setter Property="TextBlock.Text" Value="{Binding Description01.Header}" />
</Style>
</TextBlock.Style>
</TextBlock>
</Hyperlink.Inlines>
</Hyperlink>
</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0" Text="dde" />
<TextBlock Grid.Row="1" Grid.Column="1">
<Hyperlink Command="{Binding Link}"
CommandParameter="{Binding}"
Foreground="Blue" >
<Hyperlink.Inlines>
<TextBlock>
<TextBlock.Style>
<Style>
<Setter Property="TextBlock.Text" Value="{Binding Description11.Header}" />
</Style>
</TextBlock.Style>
</TextBlock>
</Hyperlink.Inlines>
</Hyperlink>
</TextBlock>
</Grid>
</Window>
And the C# Code code:
public class TestCommand : ICommand
{
public delegate void ICommandOnExecute(object parameter);
public delegate bool ICommandOnCanExecute(object parameter);
private ICommandOnExecute _execute;
private ICommandOnCanExecute _canExecute;
public TestCommand(ICommandOnExecute onExecuteMethod, ICommandOnCanExecute onCanExecuteMethod)
{
_execute = onExecuteMethod;
_canExecute = onCanExecuteMethod;
}
#region ICommand Members
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute.Invoke(parameter);
}
public void Execute(object parameter)
{
_execute.Invoke(parameter);
}
#endregion
}
public class LongDescription
{
public string Header { get; }
public string Description { get; }
public LongDescription(string header, string description)
{
Header = header;
Description = description;
}
}
public class MainWindowVM
{
public ICommand Link => new TestCommand(ExecuteCommand1, CanExecuteCommand1);
public LongDescription Description11 => new LongDescription("cell11", "result cell11");
public LongDescription Description01 => new LongDescription("cell01", "result cell01");
public bool CanExecuteCommand1(object parameter)
{
return true;
}
public void ExecuteCommand1(object parameter)
{
MessageBox.Show("Executing command 1");
}
}
It is clear that I have duplicated code in XAML ( <Hyperlink.Inlines> etc). I want to refactor it so that the code duplication is eliminated. For that I am thinking of defining the style <Hyperlink.Inlines> in ResourceDictionary and then bind it to appropriate properties in MainWindowVM.
But I am unsure how to do it, any ideas?
You can easily move the Style in a ResourceDictionary like this
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Key is required to identify the Style -->
<Style x:Key="Bind01" TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Description01.Header}" />
</Style>
<Style x:Key="Bind11" TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Description11.Header}" />
</Style>
</ResourceDictionary>
And merge the Dictionary in your Window to use the Style
Merge
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="YourDictionaryHere"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="10" />
</Style>
</ResourceDictionary>
</Window.Resources>
Use
<TextBox Style="{DynamicResource Bind01}"/>
Simplification
Instead of putting the variable Binding in a Style (or Dictionary), i suggest to write the variable Bindings directly into the Control and define the rest as Style.
More Specific: The following Markup displays a bound string as a Hyperlink which executes a ICommand when clicked.
<TextBlock>
<Hyperlink Command="{Binding Link}"
CommandParameter="{Binding}"
Foreground="Blue" >
<Hyperlink.Inlines>
<TextBlock>
<TextBlock.Style>
<Style>
<Setter Property="TextBlock.Text" Value="{Binding Description11.Header}" />
</Style>
</TextBlock.Style>
</TextBlock>
</Hyperlink.Inlines>
</Hyperlink>
</TextBlock>
We could instead define a Style for a Button which looks (and does) the same, but the variable Binding can be set directly via Content.
Button Style
<Style x:Key="LinkStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<TextBlock>
<Hyperlink Command="{Binding Link}" CommandParameter="{Binding}">
<Run Text="{TemplateBinding Content}"/>
</Hyperlink>
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Apply style to Elements in Grid (replace TextBlock with styled Buttons)
<TextBlock Grid.Row="0" Grid.Column="0" Text="ddk" />
<Button Grid.Row="1" Grid.Column="1"
Content="{Binding Description01.Header}"
Style="{DynamicResource LinkStyle}">
<TextBlock Grid.Row="1" Grid.Column="0" Text="dde" />
<Button Grid.Row="1" Grid.Column="1"
Content="{Binding Description11.Header}"
Style="{DynamicResource LinkStyle}">
Screens (dashed Lines are Gridlines)
Edit
To set the Command of the Hyperlink we use the Command Property of the Button to set the Binding. Therefore we must add a TemplateBinding in our Style. Replace the "Hard Coded" Command with a TemplateBinding to the Button Command. Do the same for the Commandparameter.
<Hyperlink Command="{TemplateBinding Command}"
CommandParameter="{Templatebinding Commandparameter}"
Foreground="Blue" >
And set the Command and the CommandParameter in the styled Button
<TextBlock Grid.Row="0" Grid.Column="0" Text="ddk" />
<Button Grid.Row="1" Grid.Column="1"
Content="{Binding Description01.Header}"
Command="{Binding YOURCOMMANDHERE}"
CommandParameter="{YOURPARAMETER}"
Style="{DynamicResource LinkStyle}">
<TextBlock Grid.Row="1" Grid.Column="0" Text="dde" />
<Button Grid.Row="1" Grid.Column="1"
Content="{Binding Description11.Header}"
Command="{Binding YOUROTHERCOMMANDHERE}"
CommandParameter="{YOUROTHERPARAMETER}"
Style="{DynamicResource LinkStyle}">