Question: How do I find the maximum and minimum x-axis value of a RadCartesianChart's current viewport using DateTimeCategoricalAxis?
I can easily find the maximum/minimum values of the dataset and, if I were to use a DateTimeContinousAxis(unfortunately not an option), I could simply use ActualRange and ActualVisibleRange. However using DateTimeCategoricalAxis is a completely different story, since I cannot accurately find the datapoints within the current viewport. I even tried using the PlotAreaClip but to no avail. Not to mention, PlotAreaClip does not take into account the zoom size, which becomes a problem.
Any help would be great appreciated!
Here is a simplified version of my code:
XAML:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<telerik:RadCartesianChart ZoomChanged="Zooming">
<telerik:RadCartesianChart.Behaviors>
<telerik:ChartPanAndZoomBehavior DragMode="Pan"
PanMode="Both"
ZoomMode="None" />
</telerik:RadCartesianChart.Behaviors>
<telerik:RadCartesianChart.Series>
<telerik:OhlcSeries ItemsSource="{Binding Bars}"
OpenBinding="Open"
HighBinding="High"
LowBinding="Low"
CloseBinding="Close"
CategoryBinding="Date">
</telerik:OhlcSeries>
</telerik:RadCartesianChart.Series>
<telerik:RadCartesianChart.HorizontalAxis>
<telerik:DateTimeCategoricalAxis DateTimeComponent="Ticks"
ShowLabels="False"
PlotMode="OnTicks"
MajorTickInterval="100">
</telerik:DateTimeCategoricalAxis>
</telerik:RadCartesianChart.HorizontalAxis>
<telerik:RadCartesianChart.VerticalAxis>
<telerik:LinearAxis HorizontalLocation="Right"
RangeExtendDirection="Both"
Minimum="{Binding PriceMinimum, Mode=OneWay}"
Maximum="{Binding PriceMaximum, Mode=OneWay}"
MajorStep="{Binding PriceMajorStep, Mode=OneWay}" />
</telerik:RadCartesianChart.VerticalAxis>
<telerik:RadCartesianChart.Grid>
<telerik:CartesianChartGrid MajorXLinesRenderMode="All"
MajorLinesVisibility="XY" />
</telerik:RadCartesianChart.Grid>
</telerik:RadCartesianChart>
</Grid>
</UserControl>
CODE BEHIND:
public void Zooming(object sender, ChartZoomChangedEventArgs e)
{
RadCartesianChart chart = sender as RadCartesianChart;
if (chart != null)
{
//PlotAreaClip.Location.X (PlotAreaClip.X also works) to get left side of clip
//PlotAreaClip.Right to get right side of clip
DataTuple dtMin = chart.ConvertPointToData(new Point(chart.PlotAreaClip.Location.X - Math.Abs(chart.PanOffset), chart.PlotAreaClip.Y), chart.HorizontalAxis, chart.VerticalAxis);
DataTuple dtMax = chart.ConvertPointToData(new Point(chart.PlotAreaClip.Right - Math.Abs(chart.PanOffset), chart.PlotAreaClip.Y), chart.HorizontalAxis, chart.VerticalAxis);
object xMin = dtMin.FirstValue;
object xMax = dtMax.FirstValue;
//these numbers are VERY inconsistent, especially when you zoom in!
}
}
The Telerik control being mentioned can be found here.
If anyone have any suggestions, I would greatly appreciate it! Thank you!
After doing some digging, I found this thread with the following solution:
private DataPoint[] FindFirstLastVisiblePoints(CategoricalSeries series)
{
DataPoint firstPoint = null;
DataPoint lastPoint = null;
RadRect plotArea = this.radChart1.PlotAreaClip;
foreach (DataPoint point in series.DataPoints)
{
if (point.LayoutSlot.IntersectsWith(plotArea))
{
if (firstPoint == null)
{
firstPoint = point;
}
lastPoint = point;
}
}
return new DataPoint[] { firstPoint, lastPoint };
}
Basically, this method takes the current PlotAreaClip and returns the first and last datapoint within the current ViewPort. I really hope this helps others in the future!
Related
I don't know what setting I'm missing, in the screenshot it looks like I set the Segments with a negative X axis, but it doesn't.
.xaml
<Window x:Class="Playground.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Title="Test" Height="220" Width="200" Loaded="Window_Loaded">
<Canvas Width="100" Height="100" Background="Black" Margin="50">
<Path Stroke="Red" StrokeThickness="2" Name="myPath" />
</Canvas>
</Window>
.cs
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var _pathFigure = new PathFigure() { Segments = new PathSegmentCollection() };
var _pathCollection = new PathFigureCollection();
_pathCollection.Add(_pathFigure);
myPath.Data = new PathGeometry() { Figures = _pathCollection };
var pts = new List<Point>()
{
new Point(50,50),
new Point(1,50),
new Point(50,50),
};
_pathFigure.Segments.Clear();
_pathFigure.StartPoint = pts.First();
foreach (var p in pts)
{
var segment = new LineSegment();
segment.Point = new Point(p.X, p.Y);
_pathFigure.Segments.Add(segment);
}
}
Screenshot:
This is caused by the miter join between the two segments. The miter point between the lines is the crossing point between them. However, when they are parallel, like in your case, this crossing point goes into infinity. WPF has a certain setting - StrokeMiterLimit, which guarantees that the miter extension will not exceed a certain limit, which is X/2 times the line stroke thickness. By default it is 10, so you have "error" that is 10/2 times the stroke thickness, which is 2, therefore - 10. You may set it to 1, or use StrokeLineJoin.Bevel, or StrokeLineJoin.Round, if you don't actually need a miter join.
I set the autocompletebox just like this:
<StackPanel Orientation="Horizontal">
<StackPanel Width="120">
<Label Content="Address"/>
<Controls:AutoCompleteBox x:Name="AddressBox" MaxDropDownHeight="300" Populating="Address_Populating"/>
</StackPanel>
<StackPanel Width="120" Margin="40, 0, 0, 0">
<Label Content="Port"/>
<TextBox x:Name="PortBox" />
</StackPanel>
<Button x:Name="ConnectButton" Content="Connect" Margin="40, 0, 0, 0" VerticalAlignment="Bottom" Width="80" Height="35" Click="ConnectButton_Clicked"/>
</StackPanel>
But the max number of items displayed in the dropdown window is only 3. I am sure that the candidate number is larger than 3. I want to increase the number of items which will be displayed in the dropdown window.
For example, i want to show 15 candidateAddress's items. And the dropdown window will be displayed and 3 items will be showed firstly. But I hope it can show 5 items firstly, which means that the display area should be expanded.
The logical code of this control is:
private void Address_Populating(object sender, PopulatingEventArgs e)
{
string dirFile = "../../Config/Address.config";
if (File.Exists(dirFile))
{
var candidateAddress = new List<string>();
string input = null;
using (StreamReader sr = File.OpenText(dirFile))
{
while ((input = sr.ReadLine()) != null)
{
candidateAddress.Add(input);
}
}
AddressBox.ItemsSource = candidateAddress;
AddressBox.PopulateComplete();
}
else
{
System.Windows.MessageBox.Show("Address.config does not exist");
}
}
i can't add a pic to comment. so i add it here.
top pic : MaxDropDownHeight=50
bottom: MaxDropDownHeight=300
you mean like this?
you can creat a new project:
<Grid>
<control:AutoCompleteBox x:Name="AddressBox" FontSize="30" MaxDropDownHeight="300"
Populating="Address_Populating" Margin="0,0,0,287.283" />
</Grid>
background code:
private void Address_Populating(object sender, PopulatingEventArgs e)
{
List<int> lst = new List<int>();
for (int i = 10; i < 25; i++)
{
lst.Add(i);
}
AddressBox.ItemsSource = lst;
AddressBox.PopulateComplete();
}
the reason is found by kylejan:
i found the reason. the display area will be influenced by the former window. for example, the window i put this control on is opend by the main window. then this autocomplete box will be influenced by the size of the main window
There is no easy way (included in the Framework) to exactly set the number of items to be displayed. But this example gives you some code to create an attached property that manipulates the maxheight property in order to display any specified number of items.
So one of my latest side projects is developing a application detection and populating assistant. Programmatically I am absolutely fine populating the backend code for what I want accomplished. But I've run into a road block on the GUI. I need a GUI that is a Quarter circle that extends from the task bar to the bottom right of a standard windows operating system. When the user doubleclicks on the application, the circle rotates into view. I can do this with a typical windows form that has a transparent background and a fancy background image. But the square properties of the form will still apply when the user has the application open. And I do not want to block the user from higher priority apps when the circle is open.
I'm not really stuck on any one specific programming language. Although, I would prefer that it not contain much 3d rendering as it is supposed to be a computing assistant and should not maintain heavy RAM/CPU consumption whilst the user is browsing around.
Secondarily, I would like the notches of the outer rings to be mobile and extend beyond the gui a mere centimeter or so.
I would not be here if I hadn't had scoured the internet for direction on this capability. But what I've found is application GUI's of this nature tend to be most used in mobile environments.
So my questions are: How can I accomplish this? What programming language can I write this in? Is this a capability currently available? Will I have to sacrifice user control for design?
I wrote out some code doing something close to what you described.
I’m not sure to understand how you do want the circle to appear, so I just let a part of it always visible.
And I didn’t get the part about the mobile outer ring.
Creating and placing the window
The XAML is very simple, it just needs a grid to host the circle’s pieces, and some attributes to remove window decorations and taskbar icon:
<Window x:Class="circle.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Circle"
Width="250"
Height="250"
AllowsTransparency="True"
Background="Transparent"
MouseDown="WindowClicked"
ShowInTaskbar="False"
WindowStyle="None">
<Grid Name="Container"/>
</Window>
To place the window in the bottom right corner, you can use SystemParameters.WorkArea in the constructor:
public MainWindow()
{
InitializeComponent();
var desktopDim = SystemParameters.WorkArea;
Left = desktopDim.Right - Width;
Top = desktopDim.Bottom - Height;
}
Creating the shape
I build the circle as a bunch of circle pieces that I generate from code behind:
private Path CreateCirclePart()
{
var circle = new CombinedGeometry
{
GeometryCombineMode = GeometryCombineMode.Exclude,
Geometry1 = new EllipseGeometry { Center = _center, RadiusX = _r2, RadiusY = _r2 },
Geometry2 = new EllipseGeometry { Center = _center, RadiusX = _r1, RadiusY = _r1 }
};
var sideLength = _r2 / Math.Cos((Math.PI/180) * (ItemAngle / 2.0));
var x = _center.X - Math.Abs(sideLength * Math.Cos(ItemAngle * Math.PI / 180));
var y = _center.Y - Math.Abs(sideLength * Math.Sin(ItemAngle * Math.PI / 180));
var triangle = new PathGeometry(
new PathFigureCollection(new List<PathFigure>{
new PathFigure(
_center,
new List<PathSegment>
{
new LineSegment(new Point(_center.X - Math.Abs(sideLength),_center.Y), true),
new LineSegment(new Point(x,y), true)
},
true)
}));
var path = new Path
{
Fill = new SolidColorBrush(Colors.Cyan),
Stroke = new SolidColorBrush(Colors.Black),
StrokeThickness = 1,
RenderTransformOrigin = new Point(1, 1),
RenderTransform = new RotateTransform(0),
Data = new CombinedGeometry
{
GeometryCombineMode = GeometryCombineMode.Intersect,
Geometry1 = circle,
Geometry2 = triangle
}
};
return path;
}
First step is to build two concentric circles and to combine them in a CombinedGeometry with CombineMode set to exclude. Then I create a triangle just tall enough to contain the section of the ring that I want, and I keep the intersection of these shapes.
Seeing it with the second CombineMode set to xor may clarify:
Building the circle
The code above uses some instance fields that make it generic: you can change the number of pieces in the circle or their radius; it will always fill the corner.
I then populate a list with the required number of shape, and add them to the grid:
private const double MenuWidth = 80;
private const int ItemCount = 6;
private const double AnimationDelayInSeconds = 0.3;
private readonly Point _center;
private readonly double _r1, _r2;
private const double ItemSpacingAngle = 2;
private const double ItemAngle = (90.0 - (ItemCount - 1) * ItemSpacingAngle) / ItemCount;
private readonly List<Path> _parts = new List<Path>();
private bool _isOpen;
public MainWindow()
{
InitializeComponent();
// window in the lower right desktop corner
var desktopDim = SystemParameters.WorkArea;
Left = desktopDim.Right - Width;
Top = desktopDim.Bottom - Height;
_center = new Point(Width, Height);
_r2 = Width;
_r1 = _r2 - MenuWidth;
Loaded += (s, e) => CreateMenu();
}
private void CreateMenu()
{
for (var i = 0; i < ItemCount; ++i)
{
var part = CreateCirclePart();
_parts.Add(part);
Container.Children.Add(part);
}
}
ItemSpacingAngle define the blank between two consecutive pieces.
Animating the circle
The final step is to unfold the circle. Using a rotateAnimation over the path rendertransform make it easy.
Remember this part of the CreateCirclePart function:
RenderTransformOrigin = new Point(1, 1),
RenderTransform = new RotateTransform(0),
The RenderTransform tells that the animation we want to perform is a rotation, and RenderTransformOrigin set the rotation origin to the lower right corner of the shape (unit is percent).
We can now animate it on click event:
private void WindowClicked(object sender, MouseButtonEventArgs e)
{
for (var i = 0; i < ItemCount; ++i)
{
if (!_isOpen)
UnfoldPart(_parts[i], i);
else
FoldPart(_parts[i], i);
}
_isOpen = !_isOpen;
}
private void UnfoldPart(Path part, int pos)
{
var newAngle = pos * (ItemAngle + ItemSpacingAngle);
var rotateAnimation = new DoubleAnimation(newAngle, TimeSpan.FromSeconds(AnimationDelayInSeconds));
var tranform = (RotateTransform)part.RenderTransform;
tranform.BeginAnimation(RotateTransform.AngleProperty, rotateAnimation);
}
private void FoldPart(Path part, int pos)
{
var rotateAnimation = new DoubleAnimation(0, TimeSpan.FromSeconds(AnimationDelayInSeconds));
var tranform = (RotateTransform)part.RenderTransform;
tranform.BeginAnimation(RotateTransform.AngleProperty, rotateAnimation);
}
Not actually answering this, but I liked your question enough that I wanted to get a minimal proof of concept together for fun and I really enjoyed doing it so i thought I'd share my xaml with you:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing" x:Class="WpfApplication1.Window2"
Title="Window2" Height="150" Width="150" Topmost="True" MouseLeftButtonDown="Window2_OnMouseLeftButtonDown"
AllowsTransparency="True"
OpacityMask="White"
WindowStyle="None"
Background="Transparent" >
<Grid>
<ed:Arc ArcThickness="40"
ArcThicknessUnit="Pixel" EndAngle="0" Fill="Blue" HorizontalAlignment="Left"
Height="232" Margin="33,34,-115,-116" Stretch="None"
StartAngle="270" VerticalAlignment="Top" Width="232" RenderTransformOrigin="0.421,0.471"/>
<Button HorizontalAlignment="Left" VerticalAlignment="Top" Width="41" Margin="51.515,71.385,0,0" Click="Button_Click" RenderTransformOrigin="0.5,0.5">
<Button.Template>
<ControlTemplate>
<Path Data="M50.466307,88.795148 L61.75233,73.463763 89.647286,102.42368 81.981422,113.07109 z"
Fill="DarkBlue" HorizontalAlignment="Left" Height="39.606"
Stretch="Fill" VerticalAlignment="Top" Width="39.181"/>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</Window>
And it looks like this:
I am working on an application that needs to be able to manipulate shapes and lines in WPF. My original thought was to databind a collection to ListBox and use Rectangles in the datatemplate, setting each of the fill properties to the image. This has worked well for the majority of shapes, except for circles and a few rectangles. Since re-sizing an image causes pixelation and the lines to change sizes, the result is less than stellar.
I have spent some time browsing SO and a few other sites regarding Path elements, but haven't found anything that really meets my needs. My guess is I will need to generate paths differently for each type of shape and databind them using a converter similar to Path drawing and data binding or use http://www.telerik.com/help/wpf/raddiagram-overview.html or similar rad tool.
My questions: Is there an easier way of accomplishing this or any other examples?
EDIT: I also need to be able to add text. Not sure how I can do that with a path...maybe a ContentControl?
You can draw all manner of shapes by databinding a Path.Data to a Geometry. You can generate the Geometry from a list of points. A converter is perfect for this adaptation.
For example, I draw spirals by databinding the Path.Data property to a StreamGeometry which I generate off of a list of points managed by the view model, and it works quite well for my needs:
// ViewModel ...
public class ViewModel
{
[Notify]
public IList<Point> Points { get; set; }
}
// Converter ...
public class GeometryConverter : IValueConverter
{
public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture)
{
if (value == null || value == DependencyProperty.UnsetValue)
{
return value;
}
var points = (IList<Point>)value;
var i = 0;
var newPath = new StreamGeometry();
using (var context = newPath.Open())
{
var begun = false;
for (var i = 0; i < points.Count; i++)
{
var current = points[i];
if (!begun)
{
begun = true;
context.BeginFigure(current, true, false);
}
else
{
context.ArcTo(current, new Size(radius, radius), angle, false, SweepDirection.Counterclockwise, true, true);
}
}
}
newPath.Freeze();
return newPath.GetFlattenedPathGeometry();
}
}
XAML:
<Canvas>
<Path StrokeThickness="{Binding StrokeWidth}"
Canvas.Top="{Binding Top}"
Canvas.Left="{Binding Left}"
Data="{Binding Points, Converter={StaticResource GeometryConverter}}">
<Path.Stroke>
<SolidColorBrush Color="{Binding CurrentColor}" />
</Path.Stroke>
</Path>
</Canvas>
As for the text, wouldn't it be better to bind TextBlock elements and arrange those on a 'Canvas` as needed?
Is there some way to set the Height attribute of a WPF multi-select ListBox to be a multiple of the item height, similar to setting the size attribute of an html select element?
I have a business requirement to not have half an item showing at the bottom of the list (if it's a long list with a scrollbar), and not have extra white space at the bottom (if it's a short list with all items showing), but the only method I can find to do this is to just keep tweaking the Height until it looks about right.
(What else have I tried? I've asked colleagues, searched MSDN and StackOverflow, done some general Googling, and looked at what VS Intellisense offered as I edited the code. There's plenty of advice out there about how to set the height to fit the ListBox's container, but that's the opposite of what I'm trying to do.)
Yeah, one could imagine there would be an easier way to do it (a single snapToWholeElement property). I couldn't find this property as well.
To achieve your requirement, I've wrote a little logic. Basically, In my Windows object I've a public property lbHeight which is calculate the listbox height by calculating the height of each individual item.
First, let's take a look at the XAML:
<Window
x:Class="SO.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="120" SizeToContent="Height"
Title="SO Sample"
>
<StackPanel>
<ListBox x:Name="x_list" Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=lbHeight}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Border x:Name="x" Background="Gray" Margin="4" Padding="3">
<TextBlock Text="{Binding}" />
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
Note that the ItemTemplate is somewhat non trivial. One important thing to notice is that I gave this item a Name - so I can find it later.
In the code-behind constructor I put some data in the list box:
public MainWindow( )
{
InitializeComponent( );
this.x_list.ItemsSource = Enumerable.Range( 0, 100 );
}
next, I'm implementing a findVisualItem - to find the root element of the data template. I've made this function a little generic, so it get a predicate p which identify whether this is the element I want to find:
private DependencyObject findVisualItem( DependencyObject el, Predicate<DependencyObject> p )
{
DependencyObject found = null;
if( p(el) ) {
found = el;
}
else {
int count = VisualTreeHelper.GetChildrenCount( el );
for( int i=0; i<count; ++i ) {
DependencyObject c = VisualTreeHelper.GetChild( el, i );
found = findVisualItem( c, p );
if( found != null )
break;
}
}
return found;
}
I'll use the following predicate, which returns true if the element I'm looking for is a border, and its name is "x". You should modify this predicate to match your root element of your ItemTemplate.
findVisualItem(
x_list,
el => { return ( el is Border ) ? ( (FrameworkElement)el ).Name == "x" : false; }
);
Finally, the lbHeight property:
public double lbHeight
{
get {
FrameworkElement item = findVisualItem(
x_list,
el => { return ( el is Border ) ? ( (FrameworkElement)el ).Name == "x" : false; }
) as FrameworkElement;
if( item != null ) {
double h = item.ActualHeight + item.Margin.Top + item.Margin.Bottom;
return h * 12;
}
else {
return 120;
}
}
}
I've also made the Window implementing INotifyPropertyChanged, and when the items of the list box were loaded (Loaded event of ListBox) I fired a PropertyChanged event for the 'lbHeight' property. At some point it was necessary, but at the end WPF fetched the lbHeight property when I already have a rendered Item.
It is possible your Items aren't identical in Height, in which case you'll have to sum all the Items in the VirtualizedStackPanel. If you have a Horizontal scroll bar, you'll have to consider it for the total height of course. But this is the overall idea. It is only 3 hours since you published your question - I hope someone will come with a simpler answer.
This is done by setting parent control Height property to Auto, without setting any size to the Listbox itself (or also setting to Auto).
To limit the list size you should also specify MaxHeight Property