Although this question is long-since answered, I used an alternative approach that people might find simpler than any of these solutions (even Keith Stein's excellent answer). So I'm posting it in case it might help anyone.
You can achieve rounded corners on a button without having to write any XAML (other than a Style, once) and without having to replace the template or set/change any other properties. Just use an EventSetter in your style for the button's "Loaded" event and change it in code-behind.
(And if your style lives in a separate Resource Dictionary XAML file, then you can put the event code in a code-behind file for your resource dictionary.)
I do it like this:
Xaml Style:
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<EventSetter Event="Loaded" Handler="ButtonLoaded"/>
</Style>
Code-Behind:
public partial class ButtonStyles
{
private void ButtonLoaded(object sender, RoutedEventArgs e)
{
if (!(sender is Button b))
return;
// Find the first top-level border we can.
Border border = default;
for (var i = 0; null == border && i < VisualTreeHelper.GetChildrenCount(b); ++i)
border = VisualTreeHelper.GetChild(b, i) as Border;
// If we found it, set its corner radius how we want.
if (border != null)
border.CornerRadius = new CornerRadius(3);
}
}
If you had to add the code-behind file to an existing resource dictionary xaml file, you can even have the code-behind file automatically appear underneath that XAML file in the Visual Studio Solution if you want. In a .NET Core project, just give it appropriate corresponding name (e.g if the resource Dictionary is "MyDictionary.xaml", name the code-behind file "MyDictionary.xaml.cs"). In a .NET Framework project, you need to edit the .csproj file in XML mode