How do I make a RadioButton's Bullet align top?
Asked Answered
L

3

11

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?

Lave answered 10/2, 2009 at 19:17 Comment(0)
R
17

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 &lt;" 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>
Robbie answered 19/10, 2011 at 2:9 Comment(4)
ha I just noticed I have VerticalAlignment="Top" after having said it wasn't necessary. I think it probably isn't hereRobbie
The default style of the WPF RadioButton no longer uses the BulletDecorator in Windows 8 (aero2). It appears to not require this hack, and you can just add a Grid. You still need this hack on Windows XP (luna). Maybe Windows 7 (aero) is the same as Windows 8 but I can't check this at the moment.Rajasthani
@Rajasthani interesting. I was definitely using Windows 7 back when I answered this question so I think it must only be Windows 8. However I'm still a little skeptical about what you're saying. I'm not sure what you mean by 'just add a grid' - how would it know what to center the checkbox in relation to. Can you (or some other person with free time) elaborate with an additional answer of your own with screenshots.Robbie
From memory, the Windows 8 version automatically aligned to the top (the behaviour you were after). I think what I was trying to say was that you could just use a Grid (or a StackPanel) to lay out your RadioButtons vertically and you wouldn't need to do anything else (even if the content of the RadioButton went over multiple lines). Mind you it was over a year ago; I certainly recommend reinvestigating by anyone attempting this on Windows 8.Rajasthani
L
14

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 (a Label will not work)

  • The LineBreak is to wrap the content to a new line, so the first line is created

  • The InlineUIContainer is so we can place non-text content into a TextBlock

  • The ContentPresenter is to hold the actual content, and it has a negative top margin to remove the space left by the LineBreak 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:

enter image description here

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.

Liguria answered 2/5, 2013 at 16:13 Comment(10)
That looks like it will come in really handy, nice job.Lave
Using the template some how stops the radio button from using the GroupName. This means in the above example, the user can check all three options, which means its no longer a radio button.Sauerkraut
@Sauerkraut Any property you want to pass from the main RadioButton to the templated RadioButton needs to be added to the templated RadioButton like this : GroupName="{TemplateBinding GroupName}". I didn't add them all because it'd be too much code, but that one is definitely important :)Liguria
It doesn't appear to be necessary to add 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
@Naxos I'm not sure what you mean. Perhaps you can create a new question and include your relevant code? The Margin property from using <RadioButton Margin="{TemplateBinding Margin}">in the Template should only get applied once if I'm understanding your question correctly.Liguria
What I mean is, if I add a 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
I think this must be the explanationNaxos
@Naxos Ahh I see, good to know. I was just running a quick test with Snoop and noticed both RadioButton's still exist in the UI tree, and both have their Margin property set, which is why you were seeing double the margin. In this specific case, you don't need to inherit the Margin property in the ControlTemplate, and it should only be applied once.Liguria
If I use a <TextBlock Visibility="Collapsed" /> instead of the <LineBreak /> I can set the ContentPresenter's margin to 0, and everything looks OK.Kitts
I take my previous comment back. It doesn't always work for me. I have a control that is sometimes used in a pure WPF window and sometimes used within a WinForm ElementHost. The approach of using the collapsed TextBlock is not working inside the ElementHost.Kitts
B
2

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>
Baronet answered 10/2, 2009 at 19:19 Comment(5)
I was hoping to do it without needing to override the Radio Button template since the alignment is the only thing I want to change.Lave
Do you want the radio button to sit on top of the text or do you want to have the radio button side-by-side with the button aligned on top of the row (like next to a paragraph)?Baronet
And I'd like to do it for CheckBoxes as well, which should have the same solution.Lave
I don't know of any property that lets you do either of those designs. I would use templating for that. I'll do some snooping though (blois.us/Snoop)Baronet
Thanks a ton, your answer combined with a Multibinding and StringFormat got me exactly what I needed.Lave

© 2022 - 2024 — McMap. All rights reserved.