Youtube has a nice control for votes like below screenshots,
Is there anything similar in WPF already or if I need to do it by myself, how to do it? I'm new to WPF, XAML and all, so I have this question.
I am not familiar that anything exactly like this already exists, there are Sparkline controls in some of the proprietary controls such as Telerik RadControls, but nothing exactly the same to the YouTube votes.
But it should be easy enough to create your own control using XAML.
Let's take a look, the control exists from 3 labels, a line that shows progress and two icons. To position this elements in a WPF user control, you can use a grid with three rows and two columns.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid>
</Grid>
The labels are then placed into first and last rows using the XAML attributes Grid.Row and Grid.Column. The icons in last row can be placed using a StackPanel or just creating more columns, do as you wish. Everything here is easy.
The problem is the spark line, which I suggest you create two rectangles placed on top of one another. Both should go into second row, and span through all columns, you can achieve this using Grid.ColumnSpan attribute. First rectangle represents the background, so choose a light color for it. The second one represents the actual vote counter and should be colored red or green, depending on the votes.
Give all elements a name and you are finished with XAML (except minor corrections such as Margins and Horizontal or Vertical Alignments).
In code, create three properties for user control, all of type Integer (int). One for view count and two for up and down votes. Those properties can either be bound to labels in XAML, or you can update the values manually. Read more about Data binding here: http://wpftutorial.net/DataBindingOverview.html
To correctly place the rectangle that displays vote counter, you should just calculate the percentage of upvotes based on the properties, use the following code for help:
double percentage = UpvoteCount / (double)(UpvoteCount + DownvoteCount);
Note that I cast the sum to double, to keep percentage a floating point number (or you would always get a zero). From here all you need to do is to rescale the width of the rectangle to the appropriate percentage, considering that the background rectangle spans 100%. You can do this with the following code:
voteProgress.Width = percentage * voteBackground.ActualWidth;
In this case voteProgress and voteBackground are the names of your rectangles. Youtube also uses different colors, which you can change based on your calculated percentage:
if (percentage > 0.5)
voteProgress.Fill = new SolidColorBrush(Colors.Green);
else
voteProgress.Fill = new SolidColorBrush(Colors.Red);
The percentage MUST be calculated each time the size of the control changes (or number of votes), so look into SizeChanged event.
For more information and details, please read WPF tutorial, courtesy of Christian Moser.
http://wpftutorial.net
Related
I'm developing a vision processing application using WPF and EmguCV 3.0. My issue is that the element isn't positioned correctly on-screen. I have viewed what the padding is, and it returns all sides as 0. The ImageBox element from Emgu, which is what I am using to display the images, is encapsulated in a Windows Forms Host control. I have two other ImageBox elements, which display properly. Each of the ImageBox elements are within their own tab in a TabControl. On startup, I set the width and height properties of all the ImageBoxes and their canvases.
An additional thing to note is that the other two ImageBoxes also overflow out of their boundaries, but are reset back into the boundaries after switching back and forth between the tabs. This only happens once.
Here is a link to screenshots of what the UI looks like. http://imgur.com/a/RwG17
Additionally, here is the XAML and C# code for the ImageBoxes.
<TabItem x:Name="ImageTabControlHSV">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="HSV" />
</StackPanel>
</TabItem.Header>
<Canvas x:Name="HSVImageCanvas">
<WindowsFormsHost>
<emui:ImageBox x:Name="HSVImageBox"/>
</WindowsFormsHost>
</Canvas>
</TabItem>
//Width and height properties are gotten from camera image.
HSVImageBox.Width = ratioWidth;
HSVImageBox.Height = ratioHeight;
HSVImageCanvas.Width = width;
HSVImageCanvas.Height = height;
HSVImageCanvas.MaxHeight = height;
HSVImageCanvas.MaxWidth = width;
Any help or suggestions would be greatly appreciated.
UPDATE: Putting a counter for how many times the problematic ImageBox has been selected and using Canvas.SetTop() and Canvas.SetLeft() seems to be a workaround. I would still like to know why the canvas is changing its position.
You might try performing a Canvas.SetTop(HSVImageCanvas, HSVImageCanvas.Top) and Canvas.SetLeft(HSVImageCanvas, HSVImageCanvas.Left).
Doug
I need to stretch a diagram and a grid in Telerik WPF RadGanttView control for the full windows width. But the diagram width stays 336 units anyway and 105 units for the grid. No property affects it. The code is:
<telerik:RadGanttView Grid.Row="1" HorizontalAlignment="Stretch" Margin="8,0,6,20" VerticalAlignment="Stretch"
TasksSource="{Binding GanttTasks}" Background="{DynamicResource backgroundBrush}" BorderBrush="{DynamicResource BorderBrush}"/>
Tell me please how can I increase the real width to the full window.
To provide a little more info for anyone else dealing with this...
The RadGanttView defaults to only displaying 7 days of information
This is defined as the VisibleRange of the control
The TimeRuler part of the control only expands to be wide enough to display the VisibleRange
If the VisibleRange is wider than the available display area, you can horizontally scroll, as expected and everything looks good
If the VisibleRange is narrower than the available display area, you wind up with a blank space to the right of the TimeRuler part of the control - which looks particularly bad when the window is maximized
To make the TimeRuler part of the control expand to use the available display area, you have to set the VisibleRange property on the control to a value that is high enough to extend beyond the available display area (e.g. 30+ days, etc.)
You can add a VisibleRange property to your view model and set the Start/End dates in the view model constructor (or wherever it makes sense for you):
VisibleRange = new VisibleRange
{
Start = DateTime.Today,
End = DateTime.Today.AddDays(60)
};
And then you can bind the VisibleRange property on the RadGanttView to the VisibleRange property you just added to your view model:
<telerik:RadGanttView x:Name="GanttView" TasksSource="{Binding Tasks}" VisibleRange="{Binding VisibleRange}" />
Not sure the answer is still needed here so i'll be be quick : you need to change the PixelLength. It is a timeSpan that you wanna have equal to your number of pixel in the right part viewport divided by the length of your project.
I'm trying to draw a graph-paper like grid as the background of a Canvas. This grid is different from most explanations of how to do this that I've found because the canvas can be scaled to implement zooming. What I want to do is have a series of scales of grid lines, i.e. at every 10^n units. Then, the grid lines should fade out as they become close together due to zooming. In other words if n is large, the lines associated with that grid should be darker/heavier weight than those for a smaller n.
This was easy to do in WinForms, I implemented it by overriding OnPaint and defining the color of the line to be a function of the distance to the next grid line. Lines far apart were given a heavier weight than lines close together.
I have not figured out how to do this in WPF. I can sort of get this behavior by creating a line that has a StrokeThickness according to the spacing of the grid lines, but this only works for a small range of StrokeThickness and scaling values. It would work if it were possible to define a line as having a very heavy weight, but still a small StrokeThickness.
Even doing this via implementing a custom control with OnRender is difficult because I have not found a reliable way to get the scale of the control while rendering it (the ScaleTransform is part of one of the parent controls, not the immediate parent).
Any thoughts on how to accomplish this goal would be much appreciated!
I solved this by NOT adding the grid to the canvas but by stacking the canvas on top of another control that contains the grid:
<Grid>
<Canvas x:Name="GridLayer"/>
<Canvas x:Name="DrawingLayer" />
</Grid>
When zooming events occur I simply redraw the GridLayer.
This allowed me to only draw the lines that are needed, to draw them exactly how I want them and, in my case very important because I had potentially a gazillion grid lines, I did not need to draw the lines any longer/taller than needed. This way I conserved a lot of CPU time.
Another thing to note is that I implemented my own zoom code. I did not use a RenderTransform or a ViewBox because I wanted the line to stay at the same width. All I did was keep track of the coordinates of the top left corner to support panning and the zoomlevel. As soon as one of these changes I redraw the canvases. I wrote two functions: one transforms a coordinate on the Canvas to a graph coordinate and the other one does the reverse. The first method allows me to translate cursor coordinates to graph coordinates and the second one will turn the coordinates of the graph into points that can be used to draw on the canvas.
Untested code and making a lot of assumptions about the orientation of axis:
Point Graph2Canvas(Point graphPoint)
{
var canvasPoint = new Point(graphPoint);
canvasPoint.X *= zoomLevel;
canvasPoint.Y *= zoomLevel;
canvasPoint.X -= topLeft.X;
canvasPoint.Y -= topLeft.Y;
return canvasPoint;
}
This can be optimized and the truth is I created more functions that do the same thing for collections of points.
Extra:
I ended up with a far more complex setup that looked a bit like this:
<Grid>
<Canvas x:Name="BackgroundLayer"/>
<Canvas x:Name="GridLayer"/>
<Canvas x:Name="AxisLayer"/>
<Canvas x:Name="DrawingLayer" />
<Canvas x:Name="SelectionBoxLayer"/>
<Canvas x:Name="CursorLayer"/>
</Grid>
I have a Canvas with 2 "dots" drawn on it. See this (simplified) code:
<Canvas>
<Ellipse />
<Ellipse />
<Canvas.RenderTransform>
<RotateTransform x:Name="rotateEllipse" />
</Canvas.RenderTransform>
</Canvas>
As you can see, I want to rotate the canvas using the given RotateTransform.
Next, I want to put a TextBlock near to each Ellipse (a label). However, I don't want to include this TextBlock into the Canvas because it will then rotate also. I want the text to remain horizontal.
Any idea how to solve this in an elegant way?
Something like this, should work for you
<TextBlock RenderTransform="{Binding RelativeSource={RelativeSource AncestorType=Canvas},
Path=RenderTransform.Inverse}"/>
Assign to text box transformation matrix an inverse of the transformation matrix of the Canvas.
Good question! And I'm going to guess, so please take this answer with a pinch of salt.
I believe you are trying to place text annotations next to ellipses on a rotated canvas, but these annotations need to remain horizontal. Two things you could try:
Firstly, given the XY point that you know of each ellipse from Canvas.GetTop/GetLeft, you could find its new rotated XY location by applying the RotateTransform to the ellipse location, using the formula U = M*V, where U is the output point, V is the input point (XY location of ellipse) and M is the Rotation Matrix.
Secondly, you could place a second canvas over the first (assuming they are both in a grid, the second canvas is at higher Z-index and is the same size as the underlying canvas). Call it an annotation layer. Your annotations (text labels) can appear at the new transformed locations and unrotated using this approach.
You'd have to do this in code of course, not Xaml, although you might find a binding friendly approach by creating a value converter on the TextBlock that bound to the source RotateTransform/Ellipse and did this operation for you.
Another approach would be to take the .Inverse of the RotateTransform and apply that to the textblocks, however you may still need to translate to get to the new location. Either way I think it demands some experimentation.
Best regards,
How do you get the REAL position of objects in silverlight?
I have a header image centered on the screen. When I make the browser window smaller, obviously, the header's left side goes off the screen. Finding out the actual position is good to know if you want to position objects on top of the image.
I capture the Content_Resized and I run a little test:
if (App.Current.Host.Content.ActualWidth > header.Width)
{
TEST = Canvas.GetLeft(header);
}
else
{
TEST = Canvas.GetLeft(header);
}
TEST always returns zero.
EDIT: header sits on a grid instead of a canvas. "Well, there is your problem..." So a better question might be this. How would I get the margins of an image sitting on a grid?
I probably should just answer the question but how to find the position of an element relative to another is probably something that has been answered before (by myself and others) here and elsewhere on the tinternet.
However if your goal is to place an item over an image then place the image in a Grid and then add the item as child of the Grid. That way you assign the relative position over the image as the margin of the item and let Silverlight's layout system do the rest.
As a general rule if you feel that you need to write code to move stuff about when the size of things change then unless you are writing a custom panel or something you're probably not using Silverlight layout system properly.
Edit:
Try this experiment:-
<Grid x:Name="LayoutRoot">
<Grid x:Name="headerContainer" Margin="50, 60, 0, 0" HorizontalAlignment="Left" VerticalAlignment="Top">
<Image Source="YourLargeImage" />
<Image Source="YourSmallerImage" HorizontalAlignment="Center" VerticalAlignment="Top" />
</Grid>
</Grid>
Now try changing the inner grid's Margin to move its position around the screen. Note the smaller image always remains at the top center of the large image.
I got it working.
First of all, these images are on a grid, not a canvas. But switching the grid to a canvas caused lots of other problems one of which is that I could not have the header image centered like before.
The solution was to change the margin of the smaller image sitting on top of the larger header image when the content resized like this:
blankbarimage.Margin = new Thickness((App.Current.Host.Content.ActualWidth - header.Width) / 2, 0, 0, 0);
and, by the way, you create a content resized method like this:
App.Current.Host.Content.Resized += new EventHandler(Content_Resized);
So, to answer my own question, the way you get the REAL position of object in silverlight is (if they are on a grid) by looking at their margin settings.