WPF Datagrid zoom issue - wpf

I'm working with the WPF DataGrid from the WPFToolkit and I'm having issues with trying to zoom the entire datagrid. My initial thought was that it was going to be really easy and I would just apply a scale transform to the grid and animate the ScaleX, ScaleY properties when the used clicked a button. This did not work however because the scrollbar was zoomed in making it larger. I need fixed headers and fixed columns on the datagrid so I can't simple use a scrollviewer outside of the datagrid to handle the scrolling. The second thing I tried was to just scale the font size in the grid, but this failed because on shrinking the font size the columns stay at the original width and do not shrink.
Finally I thought I had it working by using the below code which goes into the view tree for the datagrid and adds a scale transform to the Scroll Content Presenter. (Also not shown in this code, I apply a transform to the visual tree item for the headers in the same manner so that it scales as well). I thought this was working great until I tested out the horizontal scrolling after zooming. (Vertical scrolling works perfectly.) Before zooming at all the horizontal scrolling is fine, but after zooming, when I scroll horzonitally the display freaks out. It is hard to tell exactly what it is doing, but it sort of looks like the content that is scrolling off the left of the screen is "folding over" and coming back in on the left. Maybe it is just all smashing up on the left side. Does anyone have any ideas how I can get this working, hopefully without throwing out my whole datagrid that is already working quite well otherwise.
ScrollContentPresenter sp = (ScrollContentPresenter)
VisualTreeHelper.GetChild(
VisualTreeHelper.GetChild(
VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(dgMatrix, 0), 0), 0),2);
ScaleTransform st = new ScaleTransform(1, 1);
sp.LayoutTransform = st;
DoubleAnimation a = new DoubleAnimation();
a.By = 1.5;
a.AutoReverse = false;
a.Duration = new Duration(TimeSpan.Parse("0:0:0.25"));
st.BeginAnimation(ScaleTransform.ScaleXProperty, a);
st.BeginAnimation(ScaleTransform.ScaleYProperty, a);

I figured out a solution. Don't know if this is the same way you did it or not:
<toolkit:DataGrid.CellStyle>
<Style TargetType="{x:Type toolkit:DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type toolkit:DataGridCell}">
<ContentPresenter>
<ContentPresenter.LayoutTransform>
<ScaleTransform ScaleX="{Binding Path=Value, ElementName=ZoomFactor}"
ScaleY="{Binding Path=Value, ElementName=ZoomFactor}" />
</ContentPresenter.LayoutTransform>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</toolkit:DataGrid.CellStyle>
Where ZoomFactor is a slider:
<Slider x:Name="ZoomFactor"
Value="1"
Minimum=".25"
Maximum="5"
Width="180" Margin="0,0,5,0"/>

Related

WPF DataGrid top-left button set/view Content?

Trying to set the Content property of a WPF DataGrid top-left button at run time. I get the button object using the VisualTreeHelper of the DataGrid object and then I successfully set its Content property, as verified using Snoop while running the application. However, the button text is not visible. I suspect this is because there are UI elements on top of the button that use non-transparent background brushes. Upon reading the docs I see a grid that uses storyboards and a rectangle that uses gradient brushes.
Other than editing the WPF DataGrid top-left button style template, what are my options for making the button Content (text) visible?
You could entirely replace the template.
I'd prefer to bind the text of the textblock to a property in a viewmodel personally.
This could get you started.
Put this in scope of the datagrid like in your window resources or a resource dictionary merged in app.xaml.
<Style x:Key="{ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle, TypeInTargetAssembly={x:Type DataGrid}}" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="Red">
<TextBlock Text="X" Foreground="White"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Needs more work, but it shows up on a datagrid sample I have.
( This is something which has dozens of styles from experiments answering questions elsewhere in it ).
(Something funny happens, I am now posting under the same user name as before but SO insists that I must recreate my profile every time I log in. Go figure.)
Thank you Andy for the solution you proposed. For the technical reason above, I am unable to mark this question "Solved".
Now I think a quick way around this is to place a transparent label right on top of the DataGrid button, with IsHitTestVisible = false. I noticed a couple interesting things though:
It seems the button can have a Grid or a string for Content but not both;
The button is already created when the DataGrid Loaded event handler runs, e.g., a Click event handler can be added to it; however, setting the Content to a string at this time doesn't change anything. I guess Content changes when the DataGrid is populated.

WPF: How to implement custom Grid with CellSpacing?

I miss the HTML-cellspacing in WPF. Im trying to implement something similar in WPF's Grid by making a custom Grid-class overriding from Grid and then modifying MeasureOverride and ArrangeOverride to get the behaviour I want. Which is that each cell in a grid should have a fixed spacing (not padding) to each other cell. How could this be done?
I found this blog post about creating a custom grid panel that handles spacing between cells: http://daniel-albuschat.blogspot.dk/2011/07/gridlayout-for-wpf-escape-margin-hell.html
You could probably write your own panel, or perhaps even a Grid descendant, that does your own layout with cell spacing. It would be a fair bit of work.
Here's what I usually do instead, to achieve the same thing. Suppose I want a cell spacing of 3 pixels. You can accomplish that by applying a 1.5 pixel margin to each cell (so the total space between a cell and its neighbor is 1.5 + 1.5 = 3 pixels), and then another 1.5 pixel margin around the entire Grid so the outer margin is correct (1.5 pixel margin around the cell + 1.5 margin around the Grid = 3 pixels). The XAML looks like this:
<Grid Margin="1.5">
...
<Label Grid.Row="0" Grid.Column="0" Margin="1.5">...</Label>
<TextBox Grid.Row="0" Grid.Column="1" Margin="1.5">...</TextBox>
...
</Grid>
It's ugly but it works.
If most of the controls in your Grid are of the same type (e.g. if it's all Labels, or all Labels and TextBoxes), then you can use styles, instead of declaring and re-declaring the Margin on every element in the grid:
<Grid Margin="1.5">
<Grid.Resources>
<Style TargetType="Label">
<Setter Property="Margin" Value="1.5"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="1.5"/>
</Style>
</Grid.Resources>
...
<Label Grid.Row="0" Grid.Column="0">...</Label>
<TextBox Grid.Row="0" Grid.Column="1">...</TextBox>
...
</Grid>
Actually, I've found in practice that I often want uneven margins -- for example, I may want a 3-pixel margin around the top, left, and right, but no margin on the bottom (because the controls below it already have a margin of their own). So I usually don't end up using 1.5 pixels all around; I usually end up with something more complex. So I can see why they didn't add a CellSpacing; it would make the simple cases simpler, but would be useless in more complicated layouts.
But half-margins-all-around is a quick way to achieve CellSpacing, and then you can tweak the margins if you need something fancier.
You may interested with my answer here, supports uniform border and cellspacing for each child in a custom grid:
How could I put a border on my grid control in WPF?
You're looking for the Margin property. You can style the DataGridCell class.
<Style TargetType="{x:Type toolkit:DataGridCell}">
<Setter Property="Margin" Value="3" />
</Style>

How can a WPF StackPanel fill vertically from bottom to top?

I need to be able to fill a stackpanel with buttons but the buttons must appear at the bottom of the stackpanel first and populate upwards. The buttons are created dynamically and there's an unknown number of them so visual hackery just won't work. I've tried experimenting with vertical alignments but to no avail.
Like so:
<StackPanel VerticalAlignment="Bottom">
...
</StackPanel>
and to populate with buttons upward you must insert the buttons at position 0, instead of adding them.
Or you can rotate the StackPanel 180 degrees to get the buttons to stack from the bottom to the top and then rotating the buttons another 180 degrees to get them right-side-up again:
<StackPanel>
<!-- rotate all buttons inside this panel -->
<StackPanel.Resources>
<Style TargetType="Button">
<Setter Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="180"/>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<!-- rotate the stack panel -->
<StackPanel.LayoutTransform>
<RotateTransform Angle="180"/>
</StackPanel.LayoutTransform>
<!-- content -->
<Button>1</Button>
<Button>2</Button>
</StackPanel>
Another alternative is to use a DockPanel instead.
Just set the LastChildFill to false on the DockPanel.
Then set the attached Dock property to each button you are adding to Bottom before adding to the DockPanel.
example :
var button = new Button();
DockPanel.SetDock(button, Dock.Bottom);
The best way to solve the problem is to implement custom container derived from stackpanel
but quick and dirty solution if elements are added at runtime is
public Window1()
{
InitializeComponent();
for (int i = 0; i < 10; i++)
{
Button btn = new Button();
btn.Content = "Button " + i;
MyStack.Children.Insert(0, btn);
}
}
Just insert item at 0 position instead of adding them.
Try putting the StackPanel inside another container (not a StackPanel; maybe a DockPanel) and bottom-aligning it. Then when you populate the buttons, put each new one into the first position.
I found that using a UniformGrid with Column=1 gives a neat filling stack, or set Rows=1 give a neat horizonatally filled stack. And adding from the index 0 will work from bottom up.
Love the transforms solution by Nir. I was wondering if it could be done using transforms.
One caveat, though: Don't use the transforms trick on a ScrollView based control such as a ListBox because the scroll bar operation will be inverted from the content. Hilarious to watch, as long as you're not the end user. ;>

Scaling/resizing the diameter of all bubbles on a bubble chart (in WPF or Silverlight)

I have a bubble chart in a WPF application with LOTS of points in a BubbleSeries. The automatically drawn sizes of the bubbles result in so much overlap of the plotted bubbles, that most of the bubble points are obscured. The drawn bubble size does not change if I alter the data reduce the SizeValues of all the plotted points (some sort of hidden logic seems to be determining how to automatically scale the SizeValues when drawing the bubbles).
How can I reduce the diameter of every bubble by 75% (so each bubble's diameter is one fourth the normal automatic size)?
Thanks, Alan
[I am working with the charting/data visulaization controls in the June 2009 WPF control toolkit, but I think the same question and answer probably applies to Silverlight 3 bubble charts.]
Creating the Style:
<Style x:Key="BubbleDataPointStyle" TargetType="chartingToolkit:BubbleDataPoint">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="chartingToolkit:BubbleDataPoint">
<Grid RenderTransformOrigin=".5,.5">
<Grid.RenderTransform>
<ScaleTransform ScaleX=".25" ScaleY=".25" />
</Grid.RenderTransform>
<controlsToolkit:Viewbox x:Name="viewbox">
<Ellipse Width="1" Height="1" />
</controlsToolkit:Viewbox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Add the Style to your data points:
<chartingToolkit:Chart>
<chartingToolkit:Chart.Series>
<chartingToolkit:BubbleSeries
ItemsSource="{Binding ObjectCollection}"
IndependentValuePath="AxisX"
DependentValuePath="AxisY"
SizeValuePath="Size"
DataPointStyle="{StaticResource BubbleDataPointStyle}" />
</chartingToolkit:Chart.Series>
</chartingToolkit:Chart>
If I understand the question correctly, you want to be able to scale your content without actually having to redraw it, correct? If so, you should have a look at the Viewbox class.
Not sure if there's something like this in WPF toolkit charts but in amCharts for WPF there are MinBulletSize/MaxBulletSize properties to control scaling of the bubbles. I think there must be something along these lines in WPF/Silverlight toolkit charts too.

WPF Style Trigger

I change the FontSize of Text in a Style trigger, this causes the Control containing the text to resize as well. How can I change the Fontsize without affecting the parent's size?
A nice trick to isolate an element from its parent layout wise is to place the element in a Canvas
In the markup below there are two copies of your element
The first is hidden and establishes the size of your control
The second is visible but wrapped in a Canvas so its layout size does not affect the parent.
<Parent>
<Grid>
<Element Visibility="Hidden"/>
<Canvas>
<Element />
</Canvas>
<Grid>
</Parent>
You can increase the Padding at the same time you decrease the FontSize - this will cause the calculated height of the Button to remain the same:
<StackPanel>
<Button Content="ABC">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="FontSize" Value="20"/>
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="FontSize" Value="12"/>
<Setter Property="Padding" Value="5"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Margin="0,20" Content="123" FontSize="20"/>
<Button Content="Do Re Mi" FontSize="20"/>
</StackPanel>
You can do the reverse and set a negative Padding if the FontSize is increasing, as well.
You could also use a binding from FontSize to Padding to accomplish the same thing in a general way, but if you're only dealing with a fixed set of FontSizes it would be easier to just hardcode it as above.
There is absolutely NO need for hard-coded widths, crazy measure overrides, tricky bindings, or anything of that sort.
The solution is actually incredibly simple. Instead of changing the font size in a style trigger, create a simple control template for your button with a RenderTransform applied to the content presenter element. Add a ScaleTransform to the RenderTransform. Inside a IsPressed trigger definition set the vertical and horizontal scales on the ScaleTransform to a smaller ratio, say 0.8.
Using a RenderTransform will keep the layout of the pressed button the same with, so it won't influence the position of the other elements. By contrast, using a LayoutTransform would have actually caused the button container to shrink and the parent container's ArrangeOverride method would cause the adjacent buttons to move to fill the extra space.
I'm really busy right now so I'll leave the actual implementation up to you! ;-)
http://msdn.microsoft.com/en-us/library/system.windows.media.scaletransform.aspx
I am creating a ControlTemplate for a ButtonControl so it looks like a label (flat text, no borders) with triggers for IsKeyboardFocused, IsPressed, IsDefaulted etc.
The IsPressed is defined to drop the FontSize (from default of 30) down to 28. To give a pressed animation effect.
One use of these Buttons is a horizontal StackPanel of Button, separated by vertical separators. When the IsPressed trigger is fired on a button and it is resized, the entire row of buttons gets re adjusted, which is not a pleasing visual effect.
My preference is for a template based solution, to avoid introducing new controls in order to provide overrides. The only problem with the hard coded size approach is internationalisation, other languages will increase the orginal size.
The solution I am going with is to set the minWidth in C# after the button's DesiredSize has been calculated. Note that Width is NaN even after the Button is rendered hence the use/existence of DesiredSize. Later I will try and XAMLize the C#.
What kind of control are you using? If this is a HeaderedControl like a GroupBox or TabItem then you need to specifically set the HeaderTemplate like this:
<DataTemplate x:Key="MyHeaderTemplate">
<TextBlock Text="{Binding}" Fontsize="14" FontWeight="Bold" />
</DataTemplate>
I can think of a couple of things you could try:
You can override the Measure Pass of the control - when a control is rendered in WPF it undergoes two passes. The first is a 'measure pass', where the control comes up with what sizes that it wants to be. The second is the 'arrange pass', where it actually lays out the control. WPF provides a method called MeasureOverride. If you override this method you can provide custom behavior that can be used to adjust the size of the control.
Note - I believe that you will have to call the Measure method all of your controls children during this override in order to get your control to lay out properly.
Hard code the height and width on the control - this will override the control's DesiredSize with your values. While generally a not the greatest of ideas, it will work.

Resources