I use a specific Border control multiple times throughout my app. This Border control is displayed as part of a CollectionView. I used VisualStateManager to control the Border color when the Border and it's contained item was selected within the CollectionView. Here is where the Border control was defined:
<CollectionView Margin="0,10,0,0" ItemsLayout="HorizontalList" HorizontalOptions="Center"
ItemsSource="{Binding Source={x:Static vm:WaxItVM.SnowSourceButtons}}"
SelectionMode="Single" SelectedItem="{Binding SelectedSnowSourceButton}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Border StrokeShape="RoundRectangle 10" StrokeThickness="2" Margin="4,0,4,0">
<Image Source="{Binding IconFileName}" Margin="6,6,6,6" WidthRequest="35" HeightRequest="35" VerticalOptions="Center" HorizontalOptions="Center">
<Image.Behaviors>
<mct:IconTintColorBehavior TintColor="{AppThemeBinding Light={StaticResource Black},Dark={StaticResource White}}"/>
</Image.Behaviors>
</Image>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Here is how I used VisualStateManager in the same ContentPage file:
<ContentPage.Resources>
<Style TargetType="Border">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="Stroke" Value="{StaticResource Primary}" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White},Dark={StaticResource Black}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ContentPage.Resources>
That all worked beautifully. I then decided to extract the Border control into a separate Xaml file named SnowConditionInputButton.xaml containing a ContentView. Here is what that looked like...
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mct="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="FasterWaxer.CustomComponents.SnowConditionInputButton">
<Border StrokeShape="RoundRectangle 10" StrokeThickness="2" Margin="4,0,4,0">
<Image Source="{Binding IconFileName}" Margin="6,6,6,6" WidthRequest="35" HeightRequest="35" VerticalOptions="Center" HorizontalOptions="Center">
<Image.Behaviors>
<mct:IconTintColorBehavior TintColor="{AppThemeBinding Light={StaticResource Black},Dark={StaticResource White}}"/>
</Image.Behaviors>
</Image>
</Border>
</ContentView>
I then changed my ContentPage to:
<CollectionView Margin="0,10,0,0" ItemsLayout="HorizontalList" HorizontalOptions="Center"
ItemsSource="{Binding Source={x:Static vm:WaxItVM.SnowSourceButtons}}"
SelectionMode="Single" SelectedItem="{Binding SelectedSnowSourceButton}">
<CollectionView.ItemTemplate>
<DataTemplate>
<custom_components:SnowConditionInputButton/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
When I did that, the each CollectionView item was displayed correctly but the Border color no longer changed when I selected a CollectionView item. It seems that the ContentPage.Resources no longer applied to the Border control when I moved it to the separate ContentView. So, I tried to move VisualStateManager to the ContentView to explicitly control the Border color. When doing so, the Border still doesn't respond to being selected within the CollectionView:
<Border StrokeShape="RoundRectangle 10" StrokeThickness="2" Margin="4,0,4,0">
<VisualStateManager.VisualStateGroups>
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="Stroke" Value="{StaticResource Primary}" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White},Dark={StaticResource Black}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</VisualStateManager.VisualStateGroups>
<Image Source="{Binding IconFileName}" Margin="6,6,6,6" WidthRequest="35" HeightRequest="35" VerticalOptions="Center" HorizontalOptions="Center">
<Image.Behaviors>
<mct:IconTintColorBehavior TintColor="{AppThemeBinding Light={StaticResource Black},Dark={StaticResource White}}"/>
</Image.Behaviors>
</Image>
</Border>
How can I extract that Border control and the related VisualStateManager into a separate Xaml file for reuse? As a side note, I don't want that Border configuration to apply to all Borders throughout the app so I don't want to define it as a global Border Style that applies to all Borders. I want to selectively apply it to specific Borders or to all Borders on a page.
Thanks for your help.
You could make some changes to your code:
First, in the ContentView, give a Name to the Border. Let's call it myBorder which will be referenced in our VisualStateManger.
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
...>
<Border x:Name="myBorder" StrokeShape="RoundRectangle 10" StrokeThickness="2" Margin="4,0,4,0" HeightRequest="50" >
...
</Border>
</ContentView>
In ContentPage, make some changes to our VisualStateManager. As the VisualStateGroups is attached to Custom Control SnowConditionInputButtonm, we should set the TargetName and Property to Border like the following:
<ContentPage.Resources>
<Style TargetType="custom_components:SnowConditionInputButton">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter TargetName="myBorder" Property="Border.Stroke" Value="{StaticResource Primary}" />
<Setter TargetName="myBorder" Property="Border.BackgroundColor" Value="{AppThemeBinding Light={StaticResource White},Dark={StaticResource Black}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ContentPage.Resources>
Then easily consume Custom Control in CollectionView:
<CollectionView.ItemTemplate>
<DataTemplate>
<custom_components:SnowConditionInputButton/>
</DataTemplate>
</CollectionView.ItemTemplate>
If you don't want to use Style like the above code, you could easily define VisualStateManager in SnowConditionInputButton. It also works fine.
<custom_components:SnowConditionInputButton HeightRequest="100">
<VisualStateManager.VisualStateGroups>
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter TargetName="myBorder" Property="Border.Stroke" Value="{StaticResource Primary}" />
<Setter TargetName="myBorder" Property="Border.BackgroundColor" Value="{AppThemeBinding Light={StaticResource White},Dark={StaticResource Black}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</VisualStateManager.VisualStateGroups>
</custom_components:SnowConditionInputButton>
Hope it works for you.