I have a multiline radio button and I want the bullet to be to the left of the content (as default) aligned to the top of the radio button control. What's the easiest way to do this in XAML?
Note: Be sure to check out Rachel's answer - she takes this one stage further into a generic template
First of all don't waste your time with VerticalAlignment
or VerticalContentAlignment
(or even ControlTemplate
). They're not going to do what you want or might expect.
As described on MSDN a BulletDecorator
(which is the control that CheckBox and RadioButton uses to render a radio/check button) will set the position of the icon automatically. You have no additional control over this:
A Bullet always aligns with the first line of text when the Child object is a text object. If the Child object is not a text object, the Bullet aligns to the center of the Child object.
Unless you change the control template (unnecessary) you'll only be able to position the radio/check icon at the top if the content is text.
So if you do something like this it won't look good because you won't be able to move the icon no matter how many VerticalAlignment
properties you try to set.
<RadioButton>
<StackPanel>
<TextBlock Text="First line"/>
<TextBlock Text="Something else"/>
</StackPanel>
</RadioButton>
BUT fortunately you can put pretty much anything you want in a TextBlock
using InlineUIContainer
. The text (or content) in the first line will dictate the position of the icon automatically. If you want something underneath the first line that isn't text, just use <Linebreak/>
and then <InlineUIContainer/>
Here's an example that has an oversized TextBox
to show more clearly what's happening.
<RadioButton>
<TextBlock VerticalAlignment="Top" TextWrapping="Wrap">
<TextBlock Text="Products with <" VerticalAlignment="Center" Margin="0,0,5,0"/>
<InlineUIContainer BaselineAlignment="Center">
<TextBox FontSize="30" Width="25" Text="10" Margin="0,0,5,0"/>
</InlineUIContainer>
<TextBlock VerticalAlignment="Center" Margin="0,0,5,0">
<Run Text="days" FontWeight="Bold"/>
<Run Text="inventory" />
</TextBlock>
<LineBreak/>
<InlineUIContainer>
<StackPanel>
<CheckBox Content="Include unsold products" />
<CheckBox Content="Include something else" />
</StackPanel>
</InlineUIContainer>
</TextBlock>
</RadioButton>
I've built a relatively generic template based on Simon Weaver's answer that can be used in most situations without having to remember to customize your RadioButton.Content
all the time.
<ControlTemplate x:Key="MultiLineRadioButtonTemplate" TargetType="{x:Type RadioButton}">
<RadioButton IsChecked="{TemplateBinding IsChecked}">
<TextBlock>
<LineBreak />
<InlineUIContainer>
<ContentPresenter Margin="0,-21,0,8"
Content="{TemplateBinding ContentPresenter.Content}"
ContentTemplate="{TemplateBinding ContentPresenter.ContentTemplate}"/>
</InlineUIContainer>
</TextBlock>
</RadioButton>
</ControlTemplate>
To explain how the template works:
The
TextBlock
is there because by default, the RadioButton bullet aligns with the first line of Text if the content is a Text object (aLabel
will not work)The
LineBreak
is to wrap the content to a new line, so the first line is createdThe
InlineUIContainer
is so we can place non-text content into aTextBlock
The
ContentPresenter
is to hold the actual content, and it has a negative top margin to remove the space left by theLineBreak
object.
Here's some example content:
<StackPanel>
<RadioButton Template="{StaticResource MultiLineRadioButtonTemplate}">
<StackPanel>
<Label Content="Option 1" />
<StackPanel>
<CheckBox Content="Some setting" />
<CheckBox Content="Some other setting" />
</StackPanel>
</StackPanel>
</RadioButton>
<RadioButton Template="{StaticResource MultiLineRadioButtonTemplate}">
<StackPanel>
<Label Content="Option 2" />
<DataGrid AutoGenerateColumns="False" Height="100">
<DataGrid.Columns>
<DataGridTextColumn Header="Id" />
<DataGridTextColumn Header="Date" />
<DataGridTextColumn Header="Total" />
<DataGridTextColumn Header="Count" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</RadioButton>
<RadioButton Template="{StaticResource MultiLineRadioButtonTemplate}">
<StackPanel>
<Label Content="Option 3" />
<TextBlock TextWrapping="WrapWithOverflow" Margin="2">
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
</TextBlock>
</StackPanel>
</RadioButton>
</StackPanel>
And how it looks:
The only thing I'm really not happy about is the negative top margin for the ContentPresenter
being hard-coded, because if you ever change your font size, you have to manually adjust that margin.
It probably wouldn't be that hard to build a converter for the Margin
property that calculates the height of the line break ({TemplateBinding FontSize}
?), or even an extended version of the RadioButton control which has this behavior by default, but for now I'm fine with hard-coding -21 based on my application's default font size.
Also, you might want to add some TemplateBindings
to the RadioButton
in the Template if you want to inherit other properties from the original RadioButton
, such as margins, padding, alignment, etc. I only bound to IsChecked
for the purpose of keeping it simple.
GroupName="{TemplateBinding GroupName}"
. I didn't add them all because it'd be too much code, but that one is definitely important :) –
Liguria Margin={TemplateBinding Margin}
to the RadioButton
in the ControlTemplate
. Further, if I do this, any margin I add to consumers of the template seems to be applied twice. Is that what you'd expect? –
Naxos <RadioButton Margin="{TemplateBinding Margin}">
in the Template should only get applied once if I'm understanding your question correctly. –
Liguria TemplateBinding
for Margin
to the ControlTemplate
and then do <RadioButton Margin="8,0,0,0" Template={StaticResource MultiLineRadioButtonTemplate}" />
, the actual left margin ends up being 16. This puzzles me since I definitely need a template binding for IsChecked
and GroupName
. Since Margin
is a common property from FrameworkElement
, might WPF be applying it once to the "root" RadioButton
and then again to the one in the template? –
Naxos Override the Control.Template for the RadioButton. Here is the example from MSDN Radio Button Control Template Example
If you don't want to override the Control.Template for the radio button you can make the content a Wrapped TextBlock. see this sample
<RadioButton Name="radioButton1">
<TextBlock TextWrapping="Wrap">Here is some multiline text that does some wrapping</TextBlock>
</RadioButton>
© 2022 - 2024 — McMap. All rights reserved.