How to Update UserControl (Chart) in MVVM - wpf

I have a chart embedded in a usercontrol
PlotControl.xaml (PlotControl.xaml.cs)
This plot control is used on a View in MVVM.
NOW! when I update the UI of PlotControl (e.g. I draw vertical and horizontal markers on chart), these updates are not visible on View (unless I do double click on the View or do a Minimize-Maximize window).
Is there a way the updated UI is updated automatically on View?
View code looks like:
<Grid Margin="4">
<nms:PlotControl x:Name="PlotControl" Margin="10,10" DockPanel.Dock="Right" />
</nms:PlotControl>
Snippet from PlotControl.xaml.cs code looks like:
ChartPanel cpnl = new ChartPanel();
chart.View.Layers.Add(cpnl);
ChartMarker rightVerticalMarker = new ChartMarker(chart, MarketType.RightVertical);
rightVerticalMarker.DataPoint = new Point(30, double.NaN);
cpnl.Children.Add(rightVerticalMarker);
cpnl.UpdateLayout();
chart.UpdateLayout();
ChartMarker is simply a line Horizontal (or Vertical) defined by enum MarketType.
NOTE: I have been searching for this problem since two days in SO, but nowhere could I find the solution.

Updates can be forced by Arrange methods. Here is what I have found, which solved my problem.
Size userControlSize = this.RenderSize; //original size of userControl
Size chartSize = chart.RenderSize; // original size of chart
chart.Arrange(chart.View.PlotRect); // forcing layout elements on chart
chart.Arrange(new Rect(chartSize)); // setting the chart size to original value
Arrange(new Rect(userControlSize)); // setting the userControl size to original value

Related

How to add borders to WPF ListView in code

I have created a ListView with a GridView in code.
ListView gridList = new ListView();
GridView gridListView = new GridView ();
gridList.View = gridListView;
Now, I define a GridViewColumn, set the header, width and bindingPath. All good and the data shows up.
GridViewColumn listColumn = new GridViewColumn();
listColumn.Header = "Some Header";
listColumn.Width = 100.0;
listColumn.DisplayMemeberBinding = new Binding("Name");
gridListView.Columns.Add(listColumn);
But there are no borders/gridlines shown on display of this ListView. How can I add borders through code?
Someone described my exact problem here but no good solution mentioned
http://social.msdn.microsoft.com/Forums/en-US/fa4fa8e0-81fe-487a-8763-590062d29c06/wpf-listview-gridview-row-border?forum=wpf
The logic in WPF programming is totally different from what you've done in winforms. Everything related to UI should always be set up using XAML (as much as possible). The WPF library itself has many parts desgined mainly for use in XAML although there is always an equivalent codebehind. However that's when using codebehind may be awkward and non-intuitive (as well as straight-forward).
I understand that you want something like the ListView Grid in Winforms. In WPF that can be achieved easily if you use XAML code. Even in code behind, you can always build a Style or Template from XAML string (with the help of XamlReader). This approach is good for complex scenario but in this case I have another approach (don't use the XAML parser at all). This trick does render the grid which is good enough (and at best it can do for the trade-off of simplicity):
//we need an instance of Style to set to ListView.ItemContainerStyle
var style = new Style(typeof(ListViewItem));
//set the bottom border thickness to 1
var setter = new Setter(Control.BorderThickness, new Thickness(0,0,0,1));
style.Setters.Add(setter);
//set the border brush
var borderBrush = new LinearGradientBrush { StartPoint = new Point(0,0),
EndPoint = new Point(1,0)};
var gradStop = new GradientStop(Colors.Transparent, 0.001);
borderBrush.GradientStops.Add(gradStop);
gradStop = new GradientStop(Colors.Green, 0.001);
borderBrush.GradientStops.Add(gradStop);
gradStop = new GradientStop(Colors.Green, 0.999);
borderBrush.GradientStops.Add(gradStop);
gradStop = new GradientStop(Colors.Transparent, 0.999);
borderBrush.GradientStops.Add(gradStop);
setter = new Setter(Control.BorderBrush, borderBrush);
style.Setters.Add(setter);
yourListView.ItemContainerStyle = style;
Note that the default inner Border of each ListViewItem has a hard-coded CornerRadius of about 2, so by setting just the bottom BorderBrush to a solid brush such as Brushes.Green will show a little upwards curly line at the 2 ends of the bottom border. You can try it yourself. If this result is acceptable, the code can be shorter and simpler (because you don't have to define the GradientBrush to cut-off the 2 curly ends) like this:
setter = new Setter(Control.BorderBrush, Brushes.Green);
style.Setters.Add(setter);
If the behavior is still not what you want. You should try the approach I mentioned about using XamlReader to parse a XAML string and get an instance of whatever you want in codebehind. (you can search it yourself, it's easy to have some result).
I suggest you see this link, it contains a dynamic GridView created in code-behind that can be useful for your specific case. For the code sample that you provided, you didn't add ShowGridLines property.

WPF hit testing a rectangular area

I have a WrapPanel containing an arbitrary number of jagged sized elements. I'd like to implement drag select for my items.
It seems pretty obvious how to HitTest for a point, but how can I find all items within a rectangular area?
You may use VisualTreeHelper.HitTest with a GeometryHitTestParameters argument and a HitTestFilterCallback that checks if a Visual is a direct child of the Panel.
Something like this:
var selectedElements = new List<DependencyObject>();
var rect = new RectangleGeometry(...);
var hitTestParams = new GeometryHitTestParameters(rect);
var resultCallback = new HitTestResultCallback(
result => HitTestResultBehavior.Continue);
var filterCallback = new HitTestFilterCallback(
element =>
{
if (VisualTreeHelper.GetParent(element) == panel)
{
selectedElements.Add(element);
}
return HitTestFilterBehavior.Continue;
});
VisualTreeHelper.HitTest(
panel, filterCallback, resultCallback, hitTestParams);
It looks a little complicated, but the HitTestFilterCallback is necessary to get all Visuals in the visual tree, not only those that actually got hit. For example if your panel contains Label controls, the HitTestResultCallback will only be called for the Border and TextBlock child Visuals of each Label.
The option for controlling hit test visibility is the IsHitTestVisible property. This property allows you to control hit test visibility regardless of the brush with which the UIElement is rendered.
Also, You want to set the Fill to Transperent
<Rectangle Width="200" Height="200" Margin="170,23,12,35" Fill="Transparent" IsHitTestVisible="True" />

How can I animate a property dynamically in a Silverlight 4 UserControl?

I've run into a puzzling limitation in a Silverlight 4 UserControl.
What I'm trying to achieve is to have a panel, which slides out from a minimised state when a button is pressed, but the title bar of it should be draggable with which this maximised state can be resized.
What I've done for the sliding out is to animate the MaxHeight property of the parent Grid of this panel which works quite well even with no hardcoded Height for the panel, but I don't know how can I make this dynamic.
Trying to bind a variable from the code-behind to the 'To' parameter of the 'DoubleAnimation' didn't work, it just silently gets ignored.
As I'm creating UserControls to represent Views, the elements with x:Name properties won't get autogenerated.
I tried to work around this using the code below which mimics what happens in the autogenerated code (with the added bonus of only being done after the layout is actually loaded):
public DoubleAnimation PanelOpenMaxHeightDoubleAnimation;
private void LayoutRoot_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
var LayoutRootreference = sender as Grid;
PanelOpenMaxHeightDoubleAnimation = ((DoubleAnimation)(LayoutRootreference.FindName("PanelOpenMaxHeightDoubleAnimation")));
PanelOpenMaxHeightDoubleAnimation.To = 383;
}
This however breaks when trying to set the value of To, as FindName returns null (I have x:Name manually set in XAML for this particular animation to "PanelOpenMaxHeightDoubleAnimation"). I have the sneaking suspicion FindName can't pick DoubleAnimations up from VisualStates, only actual layout children?
I did find the documentation about XAML Namescopes at http://msdn.microsoft.com/en-us/library/cc189026(v=VS.95).aspx#UserControls, but didn't really understand what my options are from this paragraph (other than being very limited):
For the case of a UserControl, there is no equivalent template part attribute convention for parts of the UserControl in the definition XAML, nor is there a template applied at all. Nevertheless, the namescopes between definition and usage remain disconnected, because the definition namescope is defined and then effectively sealed when you package your UserControl into an assembly for reuse. A best practice here is to define your UserControl such that any value that needs to be set to modify the definition XAML is also exposed as a public property of the UserControl.
What does it mean by the last sentence?
Wondering can I do next? Should I try to generate the entire state from code?
Well, managed to work it out so I'm sharing the solution.
Instead of trying to get a reference to the DoubleAnimation in Resources, I named the Grid in the layout I want to animate and get a reference to that using the code in the original question:
var SlidePanel = ((Grid)(LayoutRootreference.FindName("SlidePanel")));
This does return the element and using that it's possible to create a DoubleAnimation and a Storyboard from scratch purely in code. I just used this code example as a starting point: http://msdn.microsoft.com/en-us/library/cc189069(VS.95).aspx#procedural_code
Best part is, you can change the DoubleAnimation.To parameter even after setting everything up in the Storyboard, so now what I'm doing is just resetting that to my calculated value every time before calling Storyboard.Begin().
It's a bit fiddly to set all these up manually, but at least it works nicely once you do.

Binding update adds news series to WPF Toolkit chart (instead of replacing/updating series)

I'm currently recoding a bar chart in my app to make use of the Chart class in the WPF Toolkit. Using MVVM, I'm binding the ItemsSource of a ColumnSeries in my chart to a property on my viewmodel. Here's the relevant XAML:
<charting:Chart>
<charting:ColumnSeries ItemsSource="{Binding ScoreDistribution.ClassScores}"
IndependentValuePath="ClassName" DependentValuePath="Score"/>
</charting:Chart>
And the property on the viewmodel:
// NB: viewmodel derived from Josh Smith's BindableObject
public class ExamResultsViewModel : BindableObject
{
// ...
private ScoreDistributionByClass _scoreDistribution;
public ScoreDistributionByClass ScoreDistribution
{
get
{
return _scoreDistribution;
}
set
{
if (_scoreDistribution == value)
{
return;
}
_scoreDistribution = value;
RaisePropertyChanged(() => ScoreDistribution);
}
}
However, when I update the ScoreDistribution property (by setting it to a new ScoreDistribution object), the chart gets an additional series (based on the new ScoreDistribution) as well as keeping the original series (based on the previous ScoreDistribution).
To illustrate this, here are a couple of screenshots showing the chart before an update (with a single data point in ScoreDistribution.ClassScores) and after it (now with 3 data points in ScoreDistribution.ClassScores):
Now, I realise there are other ways I could be doing this (e.g. changing the contents of the original ScoreDistribution object rather than replacing it entirely), but I don't understand why it's going wrong in its current form. Can anyone help?
I had the same problem. When changing the ItemsSource of a DataPointSeries it happened that old DataPoints were not removed from the chart.
My workaround in the WPF Toolkit (DataPointSeries.cs)...
A for instead of a foreach loop in LoadDataPoints(), because we change the collection:
for (int i = oldItems.Count - 1; i >= 0; i--)
Change the if in OnDataPointStateChanged() to:
if (dataPoint.State == DataPointState.Hidden || dataPoint.State == DataPointState.PendingRemoval)
This way the DataPoint will be immediately removed.
Edit:
I also had to disable the DataPoint animation, described here.
The most simple way to avoid this problem is to bind the serie ItemSource with IsAsync to True.
ItemsSource="{Binding DataItems, IsAsync=True}"
Turns out the problem was triggered by the frequency of updates in the chart, not the fact that the entire series was being replaced; changing the databound property to an ObservableCollection made no difference.
In the end, we changed our code to include a delay between changes to the underlying data and those changes being reflected in the ViewModel's bound collection property. While it does depend to an extent on the speed of the machine on which the app is running, we've settled on a 0.5sec delay between the last underlying data change and the ViewModel property updating. This prevents the chart being updated more than every 0.5secs and it seems to do the job.
At some point, I'll actually go through the WPFToolkit code and see what I can do to fix it, but for now, this workaround's worth noting.
What you are doing ought to work. The fact that it doesn't indicates WPF Toolkit has a bug.
WPF Toolkit implements OnItemsSourceChanged() on DataPointSeries to detect the case where you replace ItemsSource and call Refresh(). Refresh() has code to remove all data points (except ones that are already animating away) and then create a whole new set of DataPoints. Obviously that code has a bug in it. I looked for a minute or two at it but didn't see what was wrong. I would start by upgrading to the latest WPFToolkit release. If that doesn't fix it, you might step through the DataPointSeries.Refresh() method when the ItemsSource is changed to see what is happening there and why it isn't removing the old DataPoint objects.
Or, as you observed, you could work around the bug by just replacing the collection content instead of the collection as a whole.

Printing of WPF Window on one page

I am able to print the current Window using the following code:
PrintDialog printDialog = new PrintDialog();
if (printDialog.ShowDialog().GetValueOrDefault(false))
{
printDialog.PrintVisual(this, this.Title);
}
However if the Window does not fit the page it get truncated.
How do I make the Window fit the Page ?
I guess I need to make a graphics element first and check if this graphics fits the page, but I have found nothing so far.
There is one solution out there that lots of people are reposting as their own. It can be found here:
http://www.a2zdotnet.com/View.aspx?id=66
The problem w/ that is that it does resize your UI. So this next link takes the previous solution and resizes back to the original size when it's done. This does work, although I can't help but to think there's likely a more elegant solution out there somewhere:
http://www.slickthought.net/post/2009/05/26/Visual-Tree-Printing-in-WPF-Applications.aspx
Slickthought.net domain is defunct. Wayback Machine to the rescue.
https://web.archive.org/web/20130603071346/http://www.slickthought.net/post/2009/05/26/Visual-Tree-Printing-in-WPF-Applications.aspx
<Button Content="Print" Command="{Binding Path=PrintCommand}" CommandParameter="{Binding ElementName=ReportPanel}"></Button>
There are two important things to note here. First, I am using a WPF command to start the printing process. You don't have to do it this way, but it lets me tie the presenter to the UI pretty cleanly. The second thing is the CommandParameter. It is passing in a reference to the the ReportPanel. ReportPanel is just a WPF Grid control that wraps the title TextBlock and a Listbox that contains the actual charts. The simplified XAML is:
<Grid x:Name="ReportPanel" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock />
<ListBox/>
</Grid>
With that UI established, lets jump to the code. When the user clicks the Print button, the following WPF command is executed:
this.PrintCommand = new SimpleCommand<Grid>
{
CanExecuteDelegate = execute => true,
ExecuteDelegate = grid =>
{
PrintCharts(grid);
}
};
This is pretty simple stuff. SimpleCommand implements the ICommand interface and lets me pass in some lambda expressions defining the code I want to run when this command is fired. Clearly, the magic happens in the PrintCharts(grid) call. The code shown below is basically the same code you would find in Pankaj’s article with a couple of modification highlighted in red.
private void PrintCharts(Grid grid)
{
PrintDialog print = new PrintDialog();
if (print.ShowDialog() == true)
{
PrintCapabilities capabilities = print.PrintQueue.GetPrintCapabilities(print.PrintTicket);
double scale = Math.Min(capabilities.PageImageableArea.ExtentWidth / grid.ActualWidth,
capabilities.PageImageableArea.ExtentHeight / grid.ActualHeight);
Transform oldTransform = grid.LayoutTransform;
grid.LayoutTransform = new ScaleTransform(scale, scale);
Size oldSize = new Size(grid.ActualWidth, grid.ActualHeight);
Size sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
grid.Measure(sz);
((UIElement)grid).Arrange(new Rect(new Point(capabilities.PageImageableArea.OriginWidth, capabilities.PageImageableArea.OriginHeight),
sz));
print.PrintVisual(grid, "Print Results");
grid.LayoutTransform = oldTransform;
grid.Measure(oldSize);
((UIElement)grid).Arrange(new Rect(new Point(0, 0),
oldSize));
}
}
All right, what are these modifications? The most obvious is that I am replacing the use of the original this object (which represented the entire application window in the original code) with the Grid control that was passed in as part of the Command. So all of the measurements and transforms are executed using the Grid. The other change is that I have save the original Transform and Size of the Grid as well. The reason is that when you transform the Grid to fit to the printing page, it causes the actual application UI to change as well. This doesn't look so good on your screen, so after sending the Grid to the printer, I transform it back to its original screen layout.

Resources