I'm using MaterialDesignInXaml for WPF which provides 3rd party controls and styles. I need to edit one of these styles by changing one property.
I am using an Expander control which has a template creating a bunch of child controls. I've discovered the child 'Border' control (4 layers deep) has the property (padding) which I need to set to zero.
See this output from Snoop showing the property I need to change:
Link to image
My question is how can I do this? I've tried extending the style used by the control as follows, but it isn't changing anything so I assume I'm doing something wrong?
<Style TargetType="{x:Type Expander}"
x:Key="MaterialDesignExpanderHeadless"
BasedOn="{StaticResource MaterialDesignExpander}">
<Style.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="Padding" Value="0"></Setter>
</Style>
</Style.Resources>
</Style>
I am able to use the style like this. And I know this is working for sure:
<Expander Header="Header Content" Style="{StaticResource MaterialDesignExpanderHeadless}">
Some Content
</Expander>
You're right, this method should work. Something else is setting the border's padding.
Snoop is telling you the padding is defined by the parent template, which could be the HeaderSite (ToggleButton).
You could try to extend the ToggleButton style (BasedOn) or redefine it locally.
I have WPF application with a couple of buttons on which there's no text, only a vector-based image (using a Path object), the ControlTemplate looks like this:
<ControlTemplate x:Key="IconButtonContentTemplate" TargetType="{x:Type ButtonBase}">
<Grid Background="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}">
<Path HorizontalAlignment="Center" VerticalAlignment="Center"
Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}"
Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}"
Data="{Binding (components:ImageButtonAttachedProperties.ImagePathData), RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}"
Stretch="Uniform" Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="LightGray" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Foreground" Value="LightGray" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Gray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style TargetType="{x:Type Button}" x:Key="ClockButtonStyle" BasedOn="{StaticResource IconButtonStyle}">
<Setter Property="Template" Value="{StaticResource IconButtonContentTemplate}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="components:ImageButtonAttachedProperties.ImagePathData" Value="M69.349,65.092C68.714,65.092,68.072,64.925,67.487,64.577L46.294,51.946 46.294,21.239C46.294,19.227 47.925,17.595 49.938,17.595 51.949,17.595 53.581,19.227 53.581,21.239L53.581,47.807 71.217,58.317C72.945,59.348 73.512,61.585 72.483,63.312 71.799,64.457 70.589,65.092 69.349,65.092z M49.938,3.877C24.573,3.877 3.938,24.513 3.938,49.877 3.938,75.241 24.573,95.877 49.938,95.877 75.302,95.877 95.938,75.241 95.938,49.877 95.938,24.513 75.302,3.877 49.938,3.877z M52.876,88.467C52.882,88.395 52.897,88.324 52.897,88.25 52.897,86.615 51.572,85.29 49.937,85.29 48.302,85.29 46.977,86.615 46.977,88.25 46.977,88.324 46.994,88.395 46.999,88.467 27.994,87.032 12.783,71.822 11.349,52.817 11.423,52.822 11.492,52.838 11.567,52.838 13.202,52.838 14.527,51.513 14.527,49.878 14.527,48.243 13.201,46.918 11.567,46.918 11.492,46.918 11.422,46.935 11.349,46.94 12.783,27.933 27.994,12.722 47,11.287 46.995,11.36 46.978,11.43 46.978,11.504 46.978,13.139 48.304,14.464 49.938,14.464 51.572,14.464 52.897,13.138 52.897,11.504 52.897,11.429 52.881,11.36 52.876,11.287 71.882,12.722 87.093,27.932 88.528,46.938 88.455,46.933 88.385,46.916 88.311,46.916 86.676,46.916 85.35,48.242 85.35,49.876 85.35,51.51 86.676,52.836 88.311,52.836 88.385,52.836 88.455,52.82 88.528,52.815 87.094,71.822 71.883,87.032 52.876,88.467z" />
</Style>
My issue is that on very rare occasions the button image is not shown (99% of the time it does). The button can still be clicked, but the image on it is not shown.
I'm not sure what's causing this. The data binding on the vector image? Or the data binding on the fill color?
ImageButtonAttachedProperties.ImagePathData is System.Windows.Media.Geometry object, which describes the vector-image.
public static class ImageButtonAttachedProperties
{
public static readonly DependencyProperty ImagePathDataProperty =
DependencyProperty.RegisterAttached("ImagePathData", typeof(Geometry), typeof(ImageButtonAttachedProperties), new UIPropertyMetadata(null));
public static Geometry GetImagePathData(DependencyObject obj)
{
return (Geometry)obj.GetValue(ImagePathDataProperty);
}
public static void SetImagePathData(DependencyObject obj, Geometry value)
{
obj.SetValue(ImagePathDataProperty, value);
}
}
Any idea what I'm doing wrong here?
You're binding the desired dimensions of a template element to the final dimensions of the templated parent; this creates a circular dependency. Don't do that.
The size of the templated control (e.g., your Button) depends on the desired size of its template content (e.g., your Path). If you make the size of the content dependent on the size of the templated parent, you're just asking for trouble.
If I had to guess, you may be getting caught in a layout loop, where one layout update triggers another to occur on the next tick. These aren't always obvious (except, perhaps, by looking at CPU usage), because of the way layout updates are scheduled. I've seen this happen before, e.g., when the 'Arrange' pass invalidates the results of the 'Measure' pass. The visible effects can be rather unpredictable: sometimes things appear to be working fine, until they don't.
Get rid of the Width and Height bindings on your Path, and set the horizontal and vertical alignments to Stretch.
You should:
1) Remove the databinding on the fill color to determine if you can still re-produce the problem.
I you cannot, try it the other way around:
2) Remove the databinding on the Path (use a constant Path for testing) and test with the databinding on the fill color.
If 1) and 2) could not reproduce your problem then try:
3) Use constant values on the fill color and the databinding on the Path
If you were able to re-produce the problem in 1) or 2) you now know at least the source of the problem. The same is true for 3) but it is more complicated because 3) implies that both databindings somehow influence each other ...
4) Its also possible that your problem is burried deeper in your themes defintion - so switching custom themes off and using only Generics themes is also a way to test in order to learn the source of the issue.
5) I would also try to layout the Path on the Background of the Button to see if this behaves better with regard to your problem - see my last comment in this post:
Image fill the space on the button in WPF
You really need to post a small demo app to get a deterministic answer, otherwise, I am afraid that it is impossible to tell the exact source given only the snippets shown
I'm not sure what's causing this.
At some point the values of either ActualWidth or ActualHeight (on any control) are actually zero and those properties are read only type dependency properties. That happens when the control is Loaded, Measured, Arranged, or Rendered.
As per the FrameworkElement.ActualWidth Property
Because ActualWidth is a calculated value, you should be aware that
there could be multiple or incremental reported changes to it as a
result of various operations by the layout system. The layout system
may be calculating required measure space for child elements,
constraints by the parent element, and so on.
The question is that something is jiggering your button and causing a resize and you win the lottery by catching a zero value.
As a course of something to try, I provided an answer about housing a vector image Best way to use a vector image in WPF in two different ways, and the example I used had 3 vectors in a resizable window and to my knowledge they didn't flash except to redraw. But I had the height/width set to stretch.
Maybe change how the vector is held?
I have a NavigationView control with an AutoSearchBox displayed:
<NavigationView Style="{StaticResource CompactNavigationViewStyle}"
x:Name="NavigationView" OpenPaneLength="280"
VerticalAlignment="Stretch" VerticalContentAlignment="Stretch"
AlwaysShowHeader="False">
<NavigationView.AutoSuggestBox>
<AutoSuggestBox PlaceholderText="Search" QueryIcon="Find" Width="235"
x:Name="SearchTxt" QuerySubmitted="OnSearch" />
</NavigationView.AutoSuggestBox>
For some page I don't want to show the Search (i.e SettingPage), so on NavigationView ItemInvoked event I added this code:
private void NavigationView_ItemInvoked(NavigationView sender,
NavigationViewItemInvokedEventArgs args)
{
if (!args.IsSettingsInvoked)
{
string tag = (args.InvokedItem as string);
switch(tag)
{
case "settingpage":
case "exportpage":
SearchTxt.Visibility = Visibility.Collapsed;
break;
default:
SearchTxt.Visibility = Visibility.Visible;
break;
}
// Code to load new page to Frame here
}
}
The AutoSuggestBox is hidden from NavigationView, but the Search icon still displayed on Compact mode, how to hide this icon too?
Unfortunately, what you are looking for does not appear to be possible with the NavigationView control. This is relatively new control, however, so Microsoft may be updating it in the future.
For the technical reason, setting a value to the AutoSuggestBox property does more than just add an AutoSuggestBox to the NavigationView; it also changes some other internals of the NavigationView (such as the one specifying showing that search icon when the pane is collapsed).
The AutoSuggestBox property is an optional property, and designed to contain a search box "to allow for app-level search". This suggests that it is designed to be always visible across the entire app when present (although I could see a reasonable argument being made for having it disabled on certain pages). But simply, it looks like this is a use case the control is not designed for.
As for some ideas for work-arounds:
Option 1
The one place you can freely put whatever content you want is the NavigationView.Footer. You can implement something like the above code, and then adjust the StackPanel's visibility property.
The main downside to this option, of course, is that the footer is stuck at the bottom, which may be an odd place to put a search bar. You also will need to give the StackPanel some visual styling to mimic the hover and click effects on the rest of the NavigationView.
<StackPanel Orientation="Horizontal"
Margin="10">
<TextBlock Style="{StaticResource HamburgerMenuIconStyle}"
Text=""></TextBlock>
<TextBlock Style="{StaticResource HamburgerMenuItemTextStyle}"
Text="Home"></TextBlock>
</StackPanel>
And then the supporting styles for the Page.Resources, so that the above bindings work (you may need to play around with the numbers for the margins and font sizes, but this looked good for me):
<Page.Resources>
<Style x:Key="HamburgerMenuIconStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe MDL2 Assets"></Setter>
<Setter Property="FontSize" Value="18"></Setter>
<Setter Property="Margin" Value="5,0,0,0"></Setter>
</Style>
<Style x:Key="HamburgerMenuItemTextStyle" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"></Setter>
<Setter Property="Margin" Value="15,0,20,0"></Setter>
<Setter Property="FontSize" Value="16"></Setter>
</Style>
</Page.Resources>
Option 2
You can implement your own NavigationView, using a SplitView. This is actually not too difficult, and there is even a fantastic tutorial in the following link (which I have used for several personal projects): https://mva.microsoft.com/en-US/training-courses/windows-10-development-for-absolute-beginners-14541?l=4DLgEZ0qB_5705244527. Specifically see Video #22, if the link doesn't open that one.
Implementing your own works well enough for visual looks, and for navigational functionality. Unfortunately, however, the more advanced features in the built-in NavigationView control are considerably more time-consuming to implement, such as the built-in "Back" navigation support, and the built-in fluent design styling. You can wrap this into a UserControl or a custom Control, so that you can reuse it in other projects, but the initial time investment will still be high.
It is worth noting that the requirements you are imposing on your users by using a NavigationView in the first place should be considered (if you have not done so already):
The NavigationView was introduced in the Fall Creator's Update, so any users must have a version newer than that. Somewhere around 90% of users who have Windows 10 do, so this is pretty safe for most people.
The Back navigation functionality of the NavigationView was introduced even more recently, specifically in v10.0.17110.0. This is still in Windows Insider, and not generally released yet (I believe), so this functionality specifically may not be a good choice for a larger audience yet. The reason I point this out, is that to reach a larger audience, you would need to implement the back functionality yourself anyway, so the barrier to writing your own NavigationView may not be as high as it seems.
Hope that helps!
<NavigationViewItem
x:Name="NaviSearchItem"
Icon="Find"
Visibility="Collapsed" />
<NavigationViewItem x:Name="NaviSearchBarItem">
<AutoSuggestBox
x:Name="NaviSearchBar"
PlaceholderText="Search"
QueryIcon="Find" />
</NavigationViewItem>
You can surround the AutoSuggestBox with NavigationViewItem and place the Search Button above it. Then you need to set the visibility of NaviSearchBarItem instead of the AutoSuggestBox. This hides the AutoSuggestBox perfectly.
However, there is a tiny issue with this solution. When you click on the NaviSearchBarItem to open the Pane, the animation of tab (a blue pipe that indicates the selected item) will still slide to the NaviSearchBarItem, and then it disappears. The ideal solution should be that the blue indicator still remain on the original item. I don't know how to fix this.
I have followed the DiagramDesigner example on Codeproject for learning how to use Adorners in WPF as it fits quite a few of my needs relatively closely.
I have adapted the implementation a little, and also added my own adorner, for controlling the opacity of a control via a slider (slider on the adorner).
Following the same methods as the author, I placed the slider and other feature in a xaml style definition file as below. I am just now struggling A) to figure out how to access the slider at any level, B) how best to start hooking this up with an underlying Viewmodel that will be used for various settings (on adorners).
<Style x:Key="OpacityAdorner" TargetType="{x:Type adorners:OpacityChrome}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type adorners:OpacityChrome}">
<Grid>
<Slider x:Name="OpacitySlider" Style="{StaticResource OpacityControl}" ToolTip="Alter the opacity of the image to overlay with other images" Visibility="Collapsed"/>
<Ellipse x:Name="OpacitySliderEnable" Style="{StaticResource OpacityIcon}" ToolTip="Alter the visual opacity of the image" Visibility="Visible"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The codeproject example is here http://www.codeproject.com/Articles/22952/WPF-Diagram-Designer-Part
A) Use something like the following snippet to get the slider from the applied template.
var slider = opacityAdorner.Template.FindName("OpacitySlider", opacityAdorner) as Slider;
there are cases where the template has not yet been applied, in that case you need to preceed the previous call with the following:
opacityAdorner.ApplyTemplate();
B) The best approach for hooking up with the view model (in my opinion) is to expose the required properties as dependency properties on the OpacityChrome adorner. You then use normal Binding to hook up the new properties to the view-model, and TemplateBinding to hook them up to the template elements.
In WPF (VS2013), I'm creating a button like so:
<Button>
<Label>1</Label>
</Button>
Each of these buttons will have more to it, such as increased font size of the Label, grid row/column assignment, and I might use a binding for the label so that I can change the number. I'm creating a calculator app so I need to reuse this button 10 times (one for each number 0-9). Instead of copying/pasting this button XML 10 times, I wanted to see if I could templatize it.
I've read a little about ControlTemplate and DataTemplate, but I'm not sure if either of these are the correct thing to use. It's also not clear to me what should be a style or what should be a template.
So if someone could help me understand how to "templatize" the button and its styles (e.g. width, height, font size, etc) so that they can be easily reused, that would help a ton. Guidance is appreciated!
Use a ControlTemplate when you want to overwrite the entire template for a control, use a DataTemplate when you want to tell WPF how to draw a data object (usually the DataContext), and use ContentTemplate when you want to tell WPF how to draw the Content property of an object.
Creating a whole new ControlTemplate is quite complex. To demonstrate, check out this MSDN example for an example ControlTemplate for a Button.
In your case, I would recommend creating a Style for your button with setters for common properties such as Height, Width, Font, etc. If you want to draw your button's Content property in a custom way without completely overwriting the button template, include a ContentTemplate style setter to tell WPF how to draw the Button.Content property.
<Button Style="{StaticResource CalculatorButton}" Content="1" />
and
<Style x:Key="CalculatorButton" TargetType="{x:Type Button}">
<Setter Property="Height" Value="50"/>
<Setter Property="Width" Value="50"/>
<Setter Property="FontSize" Value="14" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding }" FontFamily="Wingdings 3" FontWeight="Bold" FontSize="18" Foreground="Navy" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
The ControlTemplate defines how the button looks, the ContentTemplate defines how the Button.Content looks, and the DataTemplate used for the ContentTemplate is defining how the data object of "1" will be drawn.
You can start with a copy of the style of the button. Use Blend (part of VS) to create that: open the context menu of the button inside the object tree, then select "Edit template" (or similar, don't have an english version at hand), then "Copy of template" (or alike).
Now you may change properties (in designer or XAML). Every button that shall have this style needs to reference this new ressource.
You need to create a new Style of a button. Learning curve is not too steep, but the benefits are enormous. You can start learning about it here: http://msdn.microsoft.com/en-us/library/ms745683(v=vs.110).aspx
Long story short: Open your project with Blend, right-click on your button, "Edit Style", "Edit a copy". If you choose to define it in Application, you can reuse it among other pages (it will be then in you App.xaml file)
Once you have the base style, edit it as much as you need.