WPF: ScrollViewer is 4px smaller than ListView - wpf

I've come across a very strange "feature" of the ListView. Hopefully someone can help me out here.
When you create a ListView on your Window, it comes with a default spacing between the border and the content. I guess it's a padding of 2 (left and right) if you look at the Snoop information. The ListBoxChrome (part of the ListView) is in my case 363px wide. The ScrollViewer inside it is 359px. There's nothing set on both these controls. Even a new project with a simple ListView has this issue.
One workaround is to give the header cells a padding of -2, but for some reason the headers won't fill until the right and leaves me with a wider gap at the right.
Someone here to help me out?
Some screenshots:
http://i.imgur.com/AKbDfwQ.png
http://i.imgur.com/pQtqMJ4.png

It's a combination of BorderThickness and Padding from the Border element (each contribute to 2px. 1px from left and 1px from right).
You can set
<ListView BorderThickness="0">
and loose 2px, however Padding atleast on Windows-8 is set directly on the Border control in the default Template and would not take any effect if set directly on the ListView
extract from default Style for ListView
<ControlTemplate TargetType="{x:Type ListView}">
<Border x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="1"
SnapsToDevicePixels="true">
simplest option is to provide a custom Style where you tweak that Padding value to 0. You can also choose to use Behavior's and get a reference to the Border control and override padding in code.
If you choose the option of code-behind to override Padding a very rough way to set the padding could be like:
public MainWindow()
{
InitializeComponent();
Loaded += (sender, args) => {
var border = (Border)lv.Template.FindName("Bd", lv);
border.Padding = new Thickness(0);
};
}
and your xaml:
<ListView x:Name="lv"
BorderThickness="0">

Related

Clipping non-opaque double progress bar in WPF

I'm trying to make a custom progress bar in WPF that has two values (the second is always equal to or higher than the first). The basic bar works ok like so:
<wpft:ClippingBorder BorderBrush="{StaticResource Border}"
Background="{StaticResource Background}"
BorderThickness="1" CornerRadius="4">
<Grid Margin="-1" x:Name="Bars">
<Border BorderBrush="{StaticResource Border}"
Background="{Binding Value2Brush}"
BorderThickness="1" CornerRadius="4"
HorizontalAlignment="Left"
Width="{Binding Value2Width}" />
<Border BorderBrush="{StaticResource Border}"
Background="{Binding Value1Brush}"
BorderThickness="1" CornerRadius="4"
HorizontalAlignment="Left"
Width="{Binding Value1Width}" />
</Grid>
</wpft:ClippingBorder>
(Where ClippingBorder is this. It's used to prevent glitching at the outer corners when the values are near 0.)
The net result is a nice rounded display:
Zoomed view, to more clearly show the rounded corners:
In particular note that both of the inner bars share the same outer border and their right edge curves to the left, just like the outer border.
This works because it draws the longer bar first, then the shorter one on top of it. However, it only works reliably when the brushes are fully opaque -- in particular if Value1Brush is partially transparent then some of Value2Brush will show through it, which I don't want.
Ideally, I want the longer bar to only draw that portion of itself that extends beyond the shorter bar -- or equivalently, to set the clipping/opacity mask of the longer bar to be transparent in the area where the shorter bar is drawn.
But I'm not sure how to do that without losing the rounded corners.
This is not a general solution, unfortunately, but it seems to work for this case. I had to give an x:Name to each of the internal borders and then put this in the code behind:
Constructor:
DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(Border))
.AddValueChanged(OuterBar, Child_ActualWidthChanged);
DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(Border))
.AddValueChanged(InnerBar, Child_ActualWidthChanged);
Handler:
private void Child_ActualWidthChanged(object sender, EventArgs e)
{
var outerRect = new Rect(OuterBar.RenderSize);
outerRect.Inflate(5, 5);
var outer = new RectangleGeometry(outerRect);
var corner = InnerBar.CornerRadius.TopLeft;
var inner = new RectangleGeometry(new Rect(InnerBar.RenderSize), corner, corner);
OuterBar.Clip = new GeometryGroup()
{
Children = { outer, inner }
};
}
The basic idea is to start with a rectangle slightly larger than what the outer bar wants to draw, and then add a rectangle that exactly matches what the inner bar wants to draw -- this clips it out of the geometry. The whole is then used as a clip region for the outer bar so that it can't draw inside the inner bar's region.
I originally tried to do this in XAML with the following, but it didn't work (the converter was not called when the width changed); I'm not sure why, but just for posterity:
<Border.Clip>
<GeometryGroup>
<RectangleGeometry Rect="{Binding ElementName=OuterBar, Path=RenderSize,
Converter={StaticResource BoundsConverter}, ConverterParameter=5.0}" />
<RectangleGeometry Rect="{Binding ElementName=InnerBar, Path=RenderSize,
Converter={StaticResource BoundsConverter}}" RadiusX="4" RadiusY="4" />
</GeometryGroup>
</Border.Clip>
(Here the converter would take the RenderSize and make a Rect, with optional inflation, similar to the code above.)
I would try to use grid's columns. Putting two columns in the grid, first on with width="auto" second as "*".Put short border in the first column the other to the second column. When you change the width of your border column will resize accordingly to your border's width.

How can I flatten a WPF border to make a seamless surface?

I have rectangles that have different colored borders to represent different statuses. I'm trying to create a placeholder shape that is just the background color. To make them all uniform sizes, I'm adding a border that is the same color as the default background for this placeholder. You can see it in the image below. However, there's a small inner line, or edge, on the border that I can't get rid of, which prevents it from looking like a "flat" surface. How can I remove that line? The border currently is:
<Border Margin="2.5" BorderBrush="{Binding exampletext, FallbackValue=#00bc00}"
Background="#788585" BorderThickness="4"
CornerRadius="3">
Do not set BorderThichkness and probably BorderBrush is also not needed:
<Border Margin="2.5"
Background="#788585"
CornerRadius="3">
Update: if you need that 4 pixel wide placeholder, you can try with Padding maybe:
<Border Margin="2.5"
Background="#00bc00" Padding="4"
CornerRadius="3">

Modify Template in code-behind

In our app we use 3rd party library components.
I need to change only one value in whole template. How can I archieve this without redefine template?
For example, controlTemplate:
<ControlTemplate TargetType="{x:Type Label}">
<Border x:Name="PART_MainBorder"
BorderBrush="Black"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter/>
</Border>
</ControlTemplate>
I need to change PART_MainBorder.BorderBrush. How can I do this?
I have found this link, but I can't believe there is no other way to do it..
Thanks.
I'm sure there are more elegant ways to do it in XAML but to answer your question template is nothing more but a cookie cuter so you cannot just start changing properties of template objects in code behind. You can modify template controls properties via control to which the template has been applied. In case of ControlTemlate it will be templated control and for DataTemplate it will be ContentPresenter used to generate content. So let's say that you have 2 Labels to which you applied template above:
<Label Content="A" x:Name="Label1"/>
<Label Content="B" x:Name="Label2"/>
an then in the code you can change Border.BorderBrush like this:
(Label1.Template.FindName("PART_MainBorder", Label1) as Border).BorderBrush = new SolidColorBrush(Colors.Red);
(Label2.Template.FindName("PART_MainBorder", Label2) as Border).BorderBrush = new SolidColorBrush(Colors.Orange);
worth noting that 2 Labels will have different BorderBrush color

Change checkbox size WPF

I want to make a bigger checkbox in WPF.
I've discovered that I need to do a control template, one example of which is found here:
http://msdn.microsoft.com/en-us/library/ms752319.aspx
If I use that code the checkbox doesn't resemble the default look. All I want to do is change the Border Width & Height attributes.
I need a control template that looks exactly like the default, from there I will just change the Width and Height. Does anyone know where I can find one? Or a better approach?
How about this?
<CheckBox>
<CheckBox.LayoutTransform>
<ScaleTransform ScaleX="2" ScaleY="2" />
</CheckBox.LayoutTransform>
</CheckBox>
You can use double values for ScaleX and ScaleY if the integer values are not exactly what you want.
Here is a possible solution found on msdn:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/98cf8a65-f4ca-4ff5-9851-c2989b91a013
The default ControlTemplates can be found on MSDN (see Default WPF Themes link).
Make sure to add the respective themes namespace to your xaml file to reference the necessary theme controls.
<theme:BulletChrome Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
RenderMouseOver="{TemplateBinding IsMouseOver}"
RenderPressed="{TemplateBinding IsPressed}"
IsChecked="{TemplateBinding IsChecked}"/>
I am not sure if you can just specify it generically, you might need to add references too.
The problem here is that you cannot really specify a border size either since the control encapsulates it.
The best solution I found is to wrap it in the ViewBox:
<Viewbox Height="46" HorizontalAlignment="Left" >
<CheckBox Content="Some Content"/>
</Viewbox>
You can use visual tree of the checkbox and when the elements you want exist, change them at runtime by explicitly setting the Width and Height. Use Peter Blois' snoop or some equivalent to see if there are named elements you can access with FindName; if not you will have to guess (e.g., some styles might have two Border elements and you must pick one) and walk the visual tree explicitly.
Keep in mind that your code should do nothing if you don't find the elements you are looking for.

What exactly does Panel.IsItemsHost do?

What is the Panel.IstItemsHost attached property used for?
I see plenty of examples of people setting it on the ItemsContainer template for an ItemsControl, but the un-documentation over at MSDN does not explain why or what advantages setting property confers.
Say I have an ItemsControl. I want to use a custom panel that swoops items in and out as you scroll; its called a SwoopPanel. Now, how do I tell the ItemsControl to use my SwoopPanel to contain the templates it creates?
The quick way is to set the ItemsPanel on the ItemsControl:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<lol:SwoopPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
However, sometimes that doesn't work for you. Maybe you wish to customize how the SwoopPanel is presented in the UI, and the only way to get around this is to change the control template of the ItemsControl. Now you can add your SwoopPanel directly to the control template and, using the property, mark it as the ItemsHost that the ItemsControl will put all the templated items it creates.
<Style TargetType="ItemsControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<Border CornerRadius="5">
<ScrollViewer VerticalScrollBarVisibility="Hidden">
<lol:SwoopPanel IsItemsHost="True"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Do you have to do it one way or the other? No. Is one more advantageous than the other? Well, the second way allows you more control of the UI, the first way is easier. Take your pick, really. I've never personally done it the second way, but I think there might be a couple of places where it might be useful.
More Explanation, Please!
While all of the above answers are technically correct, I feel they don't illustrate how IsItemsPanel correlates to the ControlTemplate and the presence (or absence) of an ItemsPresenter and the corresponding ItemsPanel property which it uses. This answer will attempt to shed light on those things and hopefully clarify when you should, or shouldn't use each.
ItemsControls, Panels and IsItemsHost, Oh my!
An ItemsControl is simply a control that displays a collection of items. It does this by first generating individual containers* to represent the items visually, then it hands those containers over to a specific panel to be laid out for display on screen. As items are added or removed, the ItemsControl adds or removes the corresponding containers from the panel as needed.
* Note: If an item is already an instance of the container type (as determined by the result of the IsItemItsOwnContainer override of the ItemsControl)--i.e. you add a ListBoxItem instance to the Items collection of a ListBox--that item is simply passed through as-is directly to the panel, acting as its own container.
The specific panel used for hosting and laying out the containers is the first one found in the ItemControl's control template that has its IsItemsHost property set to 'True'.
There are two ways to specify which panel that is:
By inserting an ItemsPresenter into the ControlTemplate to act as a placeholder for the panel specified by the ItemsPanel property. (This is the most common way.)
By inserting a Panel directly into the ControlTemplate and explicitly setting its IsItemsHost property to True.
But which do you use and why? Read on to find out!
ItemsPresenter - "Have It Your Way!"
In a typical ControlTemplate for an ItemsControl such as a ListBox, the template specifies an ItemsPresenter somewhere inside of it. Here's a simplified excerpt showing how it's used:
<Border x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
<ItemsPresenter />
</ScrollViewer>
</Border>
As you can see, there is an ItemsPresenter specified inside of a ScrollViewer in the middle of the template. What you don't see however is an actual panel to lay out the items.
So if there's no panel defined in the template, where does it come from? That's where the ItemsPanel property comes in. As its name suggests, this property defines which panel will be used to host and lay out the items. It doesn't however say where that panel appears in the ControlTemplate.
That brings us back to the ItemsPresenter. In short, it's a placeholder that essentially says "When the ItemsPanel property is set, I'll insert that panel here and set its IsItemsHost to True automatically."
The advantage of using an ItemsPresenter in the template for your ItemsControl is that you're making it very easy for consumers of your control to replace the panel without having to completely re-template your entire control.
IsItemsHost - "My Way or the Highway!"
However, what if you don't want someone to be able to change out your panel because your control depends on some custom panel implementation and anything else will break the functionality? In that case, you don't use an ItemsPresenter in your template. You instead need to specify the exact panel you want to use.
This is where IsItemsHost property comes into play. When set on a panel in the ControlTemplate, it tells that ItemsControl to use that specific panel to host the generated containers, regardless of what ItemsPanel is set to. The ItemsPanel property is essentially ignored.
Another benefit of specifying the panel directly in the template is you can then name it and access it just like any other template part.
Here's the same example as above, but rather than an ItemsPresenter, it hard-codes a SpecializedPanel to lay out the items. We indicate that's the panel we want to use to host the items by setting its IsItemsHost property to True and finally, we give it a name so we can access it directly from code.
<Border x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
<SpecializedPanel name="PART_MainPanel" IsItemsHost="True" />
</ScrollViewer>
</Border>
In this case, because the template doesn't use an ItemsPresenter and instead directly includes a panel with its IsItemsHost set to True, there is no way for the user to change out that panel short of completely replacing the entire ControlTemplate. (As mentioned before, the ItemsPanel property is ignored.)
Bringing it all home...
To recap, if you're a control author and want to give consumers of your control the flexibility to swap out the panel used to lay out your items, then define your template for your ItemsControl using an ItemsPresenter. Make sure to also set the ItemsPanel property in the template to specify a default panel.
If however, want to 'lock' which panel your control uses, then do not use an ItemsPresenter in the ControlTemplate. Instead, specify the specific panel you want to use directly in the template, then set its IsItemsHost property to True.
Note: There's technically a third scenario, which is arguably more common: You're not a control author creating something to be consumed by other users, but rather are simply re-templating an ItemsControl (like say a ListBox) for some specialized use in your own application.
In that case, since you are the ultimate consumer of the control, you most likely won't have to worry about other consumers downstream needing to change out the panel, so it's completely fine to simply specify the panel directly in your template (again, setting its IsItemsHost true) and not worry about using an ItemsPresenter and its associated ItemsPanel property as the latter, while valid, would just add unnecessary complexity without any actual benefit.
Hope this clarifies exactly what's going on.
See http://msdn.microsoft.com/en-us/library/system.windows.controls.panel.isitemshost(v=vs.90).aspx
Essentially, what this post says is that if you are replacing the ControlTemplate of a ListBox and want a new layout, set IsItemsHost=true on some panel, e.g. a StackPanel. Then any items in the ListBox will be automatically added as children of the StackPanel. If the orientation of the ListBox is Horizontal, then the ListBox will be horizontal.
The other way is to set the ItemsPanel property of the ListBox to an ItemsTemplate and in that template you have a StackPanel. In this case the ListBox items will be added to the StackPanel children just as in the first case. However, you do not need to set IsItemsHost = true, it will have absolutely no effect. This is done for you by the fact that you are setting the ItemsPanel property.

Resources