I want plot oscilloscope -like dynamic line chart in WPF and I found this library: Interactive Data Display which emerged from this library: D3 Dynamic Data Display. The advantage is that its light which is important for me.
From sample program, I can see that they do not bind the LineGraph.Points with any collection, and when I tried that it did not work, there is also no Refresh or Update method on the Graph object. Currently, I'm forced to use LineGraph.PlotY() method every time I want to update my graph.
Does anyone know if it's possible to use this library in MVVM way?
Sample code:
double[] x = new double[200];
for (int i = 0; i < x.Length; i++)
x[i] = 3.1415 * i / (x.Length - 1);
for (int i = 0; i < 25; i++)
{
var lg = new LineGraph();
lines.Children.Add(lg);
lg.Stroke = new SolidColorBrush(Color.FromArgb(255, 0, (byte)(i * 10), 0));
lg.Description = String.Format("Data series {0}", i + 1);
lg.StrokeThickness = 2;
lg.Plot(x, x.Select(v => Math.Sin(v + i / 10.0)).ToArray());
}
XAML:
<d3:Chart Name="plotter">
<d3:Chart.Title>
<TextBlock HorizontalAlignment="Center" FontSize="18" Margin="0,5,0,5">Line graph legend sample</TextBlock>
</d3:Chart.Title>
<d3:Chart.LegendContent>
<d3:LegendItemsPanel>
<d3:LegendItemsPanel.Resources>
<DataTemplate x:Key="InteractiveDataDisplay.WPF.LineGraph">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Visibility, Converter={StaticResource VisibilityToCheckedConverter}, Mode=TwoWay}"/>
<Line Width="15" Height="15" X1="0" Y1="0" X2="15" Y2="15" Stroke="{Binding Path=Stroke}" StrokeThickness="2"/>
<TextBlock Margin="5,0,0,0" Text="{Binding Path=Description}"/>
</StackPanel>
</DataTemplate>
</d3:LegendItemsPanel.Resources>
</d3:LegendItemsPanel>
</d3:Chart.LegendContent>
<Grid Name="lines"/>
</d3:Chart>
In the Plot base class, there is already a dependency property registered for the Pointsproperty. As a 'quick-fix', I added this to the LineGraphclass:
public ObservableCollection<Point> ObservablePoints
{
get { return (ObservableCollection<Point>)GetValue(ObservablePointsProperty); }
set { SetValue(ObservablePointsProperty, value); }
}
public static readonly DependencyProperty ObservablePointsProperty =
DependencyProperty.RegisterAttached(
"ObservablePoints",
typeof(ObservableCollection<Point>),
typeof(LineGraph),
new PropertyMetadata(
new ObservableCollection<Point>(),
(d, e) =>
{
var linePlot = (LineGraph)d;
var updateAction = new NotifyCollectionChangedEventHandler(
(o, args) =>
{
if (linePlot != null)
{
InteractiveDataDisplay.WPF.Plot.SetPoints(linePlot.polyline, new PointCollection((ObservableCollection<Point>)o));
}
});
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
coll.CollectionChanged -= updateAction;
}
if (e.NewValue != null)
{
var coll = (INotifyCollectionChanged)e.NewValue;
coll.CollectionChanged += updateAction;
if (linePlot != null)
{
InteractiveDataDisplay.WPF.Plot.SetPoints(linePlot.polyline, new PointCollection((ObservableCollection<Point>)e.NewValue));
}
}
}));
Then, bound my collection of points to the ObservablePoints property:
<d3:LineGraph Description="MyGraph"
ObservablePoints="{Binding Path=PointsFromMyDatamodel}"/>
The drawback is that the graph for all points is redrawn - thus the 'quick-fix'. Redrawing only added, modified or removed points would require more changes to underlying base classes...
Related
I'm trying to build a table in .NET MAUI based on a Grid Layout. This is the code:
<CollectionView ItemsSource="{Binding digitalInputs}">
<CollectionView.Header>
<Grid ColumnDefinitions="*,*,*,*">
<Label Text="Name" Grid.Column="0"/>
<Label Text="Typ" Grid.Column="1"/>
<Label Text="Status" Grid.Column="2"/>
<Label Text="Aktiv" Grid.Column="3"/>
</Grid>
</CollectionView.Header>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="services:DigitalInput">
<Grid ColumnDefinitions="*,*,*,*">
<Label Text="{Binding pName}" Grid.Column="0"/>
<Label Text="{Binding pDigitalType}" Grid.Column="1"/>
<Label Text="{Binding pValueText}" Grid.Column="2"/>
<Label Text="{Binding pActive}" Grid.Column="3"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
This is the result when running in Debug Mode on MacCatalyst (Visual Studio for Mac):
Now I wonder how I can align the header grid properly to the grid in the data template? Does someone have a suggestion on how I can improve the code to build a proper table?
Edit: This seems to be a bug in the IDE. When I change the HorizontalOptions property on the Grid in the CollectionView.Header, as a comment suggested, the XAML Hot-Reload triggers a re-rendering of the view and all of a sudden the header grid aligns correctly with the grid in the ItemTemplate.
I tested the code you provided in iOS, Windows in MAUI. And it can align the header grid properly to the grid in the data template in CollectionView. So the issue could be related with the services:DigitalInput retrieving the data, they should be correctly formatted with no blank space in those properties.
Below are the code sample and running output, hope it can shed some light for you!
XAML:
<CollectionView ItemsSource="{Binding digitalInputs}">
<CollectionView.Header>
<Grid ColumnDefinitions="*,*,*,*">
<Label Text="Name" Grid.Column="0"/>
<Label Text="Typ" Grid.Column="1"/>
<Label Text="Status" Grid.Column="2"/>
<Label Text="Aktiv" Grid.Column="3"/>
</Grid>
</CollectionView.Header>
<CollectionView.ItemTemplate>
<DataTemplate >
<Grid ColumnDefinitions="*,*,*,*">
<Label Text="{Binding pName}" Grid.Column="0"/>
<Label Text="{Binding pDigitalType}" Grid.Column="1"/>
<Label Text="{Binding pValueText}" Grid.Column="2"/>
<Label Text="{Binding pActive}" Grid.Column="3"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Code-behind:
public ObservableCollection<Model> digitalInputs { get; set; }
public NewPage1()
{
InitializeComponent();
//assign data
digitalInputs = new ObservableCollection<Model>()
{
new Model{pName="KipSchalter", pDigitalType="Zustand",pActive="OFF", pValueText="True" },
new Model{pName="KipSchalter", pDigitalType="Zustand",pActive="OFF", pValueText="True" },
new Model{pName="Digital In 3", pDigitalType="Zustand",pActive="OFF", pValueText="FALSE" },
new Model{pName="Digital In 4", pDigitalType="Zustand",pActive="OFF", pValueText="FALSE" }
}
;
BindingContext = this;
}
iOS output:
Windows output:
Update:
This seems to be a potential issue in the IDE. When changing the HorizontalOptions property on the Grid as Jason suggested, the header grid aligns correctly with the grid in the ItemTemplate.
If you mean by arranging children horizontally first and then pushing down to the next row then the current MAUI still does not support that, you can only trigger span (also span isn't changing at runtime on WinUI right now, I think the team is fixing it) or create a custom one
public class HorizontalWrapLayout : StackLayout
{
public HorizontalWrapLayout()
{
}
protected override ILayoutManager CreateLayoutManager()
{
return new HorizontalWrapLayoutManager(this);
}
}
public class HorizontalWrapLayoutManager : StackLayoutManager
{
HorizontalWrapLayout _layout;
public HorizontalWrapLayoutManager(HorizontalWrapLayout horizontalWrapLayout) : base(horizontalWrapLayout)
{
_layout = horizontalWrapLayout;
}
public override Size Measure(double widthConstraint, double heightConstraint)
{
var padding = _layout.Padding;
widthConstraint -= padding.HorizontalThickness;
var rows = new Dictionary<int, List<Size>>();
var currentRowIndex = 0;
var currentRow = new List<Size>();
rows.Add(currentRowIndex, currentRow);
foreach (var child in _layout)
{
if (child.Visibility == Visibility.Collapsed)
{
continue;
}
var childSize = child.Measure(double.PositiveInfinity, heightConstraint);
var childWidth = childSize.Width + (currentRow.Any() ? _layout.Spacing : 0);
var rowWidth = currentRow.Aggregate(0.0, (w, x) => w + x.Width);
if (rowWidth + childWidth > widthConstraint)
{
if (currentRow.Any())
{
currentRowIndex++;
currentRow = new List<Size>();
rows.Add(currentRowIndex, currentRow);
}
}
else if (currentRow.Any())
{
currentRow.Add(new Size(_layout.Spacing, 0));
}
currentRow.Add(childSize);
}
var totalWidth = 0.0;
var totalHeight = 0.0;
if (rows.Any())
{
var rowWidths = rows.Select(x => x.Value.Aggregate(0.0, (result, item) => result + item.Width)).ToList();
var rowHeights = rows.Select(x => x.Value.Any() ? x.Value.Max(i => i.Height) : 0).ToList();
totalWidth = rowWidths.Any() ? rowWidths.Max() : 0;
totalHeight = rowHeights.Any() ? rowHeights.Sum() : 0;
if (rows.Keys.Count > 1)
{
totalHeight += _layout.Spacing * (rows.Keys.Count - 1);
}
}
totalWidth += padding.HorizontalThickness;
totalHeight += padding.VerticalThickness;
var finalHeight = ResolveConstraints(heightConstraint, Stack.Height, totalHeight, Stack.MinimumHeight, Stack.MaximumHeight);
var finalWidth = ResolveConstraints(widthConstraint, Stack.Width, totalWidth, Stack.MinimumWidth, Stack.MaximumWidth);
return new Size(finalWidth, finalHeight);
}
public override Size ArrangeChildren(Rect bounds)
{
var padding = Stack.Padding;
double top = padding.Top + bounds.Top;
double left = padding.Left + bounds.Left;
double currentRowTop = top;
double currentX = left;
double currentRowHeight = 0;
double maxStackWidth = currentX;
for (int n = 0; n < _layout.Count; n++)
{
var child = _layout[n];
if (child.Visibility == Visibility.Collapsed)
{
continue;
}
if (currentX + child.DesiredSize.Width > bounds.Right)
{
// Keep track of our maximum width so far
maxStackWidth = Math.Max(maxStackWidth, currentX);
// Move down to the next row
currentX = left;
currentRowTop += currentRowHeight + _layout.Spacing;
currentRowHeight = 0;
}
var destination = new Rect(currentX, currentRowTop, child.DesiredSize.Width, child.DesiredSize.Height);
child.Arrange(destination);
currentX += destination.Width + _layout.Spacing;
currentRowHeight = Math.Max(currentRowHeight, destination.Height);
}
var actual = new Size(maxStackWidth, currentRowTop + currentRowHeight);
return actual.AdjustForFill(bounds, Stack);
}
Usage
<app:HorizontalWrapLayout
BindableLayout.ItemTemplate="{x:StaticResource HorizontalWrapLayoutItemTemplate}"
BindableLayout.ItemsSource="{Binding ControlGroups, Mode=OneWay}"
HorizontalOptions="Center"
Spacing="50"
VerticalOptions="Center" />
I'm trying in every way to get out of the loop that must create multiple RichTextBlockOverflow controls based on arbitrary input text length but without success. The HasOverflowContent property doesn't update either synchronously or asynchronously.
The variable bool "ThereIsText" I can not understand when and how to make it false to stop the loop.
The link with the text to paste in the paragraph "Run" is: text to paste.
MainPage.xaml:
<Page
x:Class="Text_Viewer_Test_UWP.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Text_Viewer_Test_UWP"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid x:Name="Menù" HorizontalAlignment="Left" Width="290" Padding="0" Margin="0,21,0,0">
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="50"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" x:Name="btnLoadText" Click="btnLoadText_Click" Content="Display text" HorizontalAlignment="Center" VerticalAlignment="Center" Width="270" Foreground="White" Height="32"/>
<TextBlock Grid.Row="1" x:Name="txtPage" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Grid>
<Grid x:Name="BaseGrid" Margin="320,10,30,10" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Black">
<ScrollViewer x:Name="PageViewer" Background="White" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Visible" VerticalScrollMode="Disabled" HorizontalScrollMode="Enabled">
<StackPanel x:Name="StackViewer" VirtualizingStackPanel.VirtualizationMode="Recycling" Orientation="Horizontal"/>
</ScrollViewer>
</Grid>
</Grid>
MainPage.xaml.cs:
public sealed partial class MainPage : Page
{
RichTextBlock TextOneRich = new RichTextBlock() { Margin = new Thickness(20) };
List<RichTextBlockOverflow> TextList = new List<RichTextBlockOverflow>();
bool ThereIsText = true;
public MainPage()
{
this.InitializeComponent();
StackViewer.Children.Add(TextOneRich);
TextOneRich.Width = 400;
TextOneRich.TextAlignment = TextAlignment.Justify;
}
private async void btnLoadText_Click(object sender, RoutedEventArgs e)
{
TextList.Clear();
TextOneRich.Blocks.Clear();
StackViewer.Children.Clear();
StackViewer.Children.Add(TextOneRich);
Paragraph paragraphText = new Paragraph();
paragraphText.Inlines.Clear();
paragraphText.Inlines.Add(new Run { Text = "PasteTextHere" });
await Task.Run(async () =>
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
TextOneRich.Blocks.Add(paragraphText);
});
}).ContinueWith(async t =>
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
TextList.Add(new RichTextBlockOverflow() { Width = 400, Margin = new Thickness(20) });
StackViewer.Children.Add(TextList[0]);
TextOneRich.OverflowContentTarget = TextList[0];
});
});
await Task.Run(async () =>
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
{
while (ThereIsText)
{
await Task.Run(async () =>
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
TextList.Add(new RichTextBlockOverflow() { Width = 400, Margin = new Thickness(20) });
StackViewer.Children.Add(TextList[TextList.Count - 1]);
TextList[TextList.Count - 2].OverflowContentTarget = TextList[TextList.Count - 1];
txtPage.Text = TextList.Count.ToString();
});
});
}
});
});
}
}
If you need to do a lot of manipulation of UI objects, and you want to keep the UI responsive while you do that(1) then you can generally just await for a single millisecond, which will allow the UI to process any input messages etc.
Trying to access the HasOverflowContent property is problematic since it requires a layout pass to complete, and that could take an arbitrary amount of time. We could just await an arbitrary amount of time - say 50ms - but that wouldn't be ideal. Instead, you can use a technique similar to the one from "Waiting for XAML layout to complete" with a slight adjustment.
This XAML and code adds 1000 lines of text to a set of RichTextBlock / RichTextBlockOverflow controls and does so while keeping the UI responsive (the ball keeps moving, and you can scroll the list at any time):
XAML:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Margin="20" HorizontalAlignment="Stretch" x:Name="container">
<Ellipse Width="20" Height="20" Margin="0, 5" Fill="red"
x:Name="animation" HorizontalAlignment="Left"/>
<Button Content="Go" Click="Go" Margin="0,0,0,5"/>
<ScrollViewer MaxHeight="500">
<StackPanel x:Name="thePanel"/>
</ScrollViewer>
</StackPanel>
</Grid>
Code:
public static class Extensions
{
// This helper function is essentially the same as this answer:
// https://stackoverflow.com/a/14132711/4184842
//
// It adds an additional forced 1ms delay to let the UI thread
// catch up.
public static Task FinishLayoutAsync(this FrameworkElement element)
{
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
// Setup handler that will be raised when layout has completed.
EventHandler<object> handler = null;
handler = (s, a) =>
{
element.LayoutUpdated -= handler;
tcs.SetResult(true);
};
element.LayoutUpdated += handler;
// Await at least 1 ms (to force UI pump) and until the Task is completed
// If you don't wait the 1ms then you can get a 'layout cycle detected' error
// from the XAML runtime.
return Task.WhenAll(new[] { Task.Delay(1), tcs.Task });
}
}
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
// Simple animation to show the UI is not frozen
BadUiAnimation_DontDoThis();
}
// Very VERY bad way of doing animation, but it shows
// that the UI is still responsive. Normally you should
// use StoryBoards to do animation.
void BadUiAnimation_DontDoThis()
{
DispatcherTimer dt = new DispatcherTimer();
dt.Interval = TimeSpan.FromMilliseconds(33);
int delta = 4;
const int width = 20;
dt.Tick += (s, a) =>
{
var leftOffset = animation.Margin.Left;
if (leftOffset + delta < 0)
{
delta *= -1;
leftOffset = 0;
}
else if (leftOffset + delta + width > container.ActualWidth)
{
delta *= -1;
leftOffset = container.ActualWidth - width;
}
else
{
leftOffset += delta;
}
animation.Margin = new Thickness(leftOffset, 5, 0, 5);
};
dt.Start();
}
private async void Go(object sender, RoutedEventArgs e)
{
// Helper function
void AppendSimpleString(string s)
{
RichTextBlock rtb = new RichTextBlock();
rtb.Blocks.Add(CreateParagraphWithText(s));
thePanel.Children.Add(rtb);
}
// Another helper function
Paragraph CreateParagraphWithText(string s)
{
var p = new Paragraph();
var r = new Run();
r.Text = s;
p.Inlines.Add(r);
return p;
}
// Disable the button so you can't click it again until the
// insertion is over
(sender as Button).IsEnabled = false;
thePanel.Children.Clear();
AppendSimpleString($"Begin...{Environment.NewLine}");
// Generate some dummy strings to add to the page
var strings = new StringBuilder();
for (int i = 0; i < 1000; i++)
strings.Append($"This is line {i + 1}{Environment.NewLine}");
string text = strings.ToString();
// Create initial block with far too much text in it
var source = new RichTextBlock();
source.MaxHeight = 100;
source.Blocks.Add(CreateParagraphWithText(text));
thePanel.Children.Add(source);
// Create the first overflow and connect it to the original textblock
var prev = new RichTextBlockOverflow
{
MaxHeight = 100,
Margin = new Thickness(0, 10, 0, 0)
};
thePanel.Children.Add(prev);
source.OverflowContentTarget = prev;
// Wait for layout to complete so we can check the
// HasOverflowContent property
await prev.FinishLayoutAsync();
// Keep creating more overflows until there is no content left
while (prev.HasOverflowContent)
{
var next = new RichTextBlockOverflow
{
MaxHeight = 100,
Margin = new Thickness(0, 10, 0, 0)
};
thePanel.Children.Add(next);
prev.OverflowContentTarget = next;
// Wait for layout to complete, which will compute whether there
// is additional overflow (or not)
await prev.FinishLayoutAsync();
prev = next;
};
AppendSimpleString($"Done!{Environment.NewLine}");
// Enable interaction with the button again
(sender as Button).IsEnabled = true;
}
}
(1): Note that you probably want to do something to limit interaction with your UI while this is happening, which might require you to disable some controls or otherwise make sure the user doesn't mess with your app's state. The sample does this by disabling and then re-enabling the button.
I want to create a grid view with varying cell size. But due to the inbuilt windows feature in which the cells adjust themselves in the row, I get the following result.
But I want to get a grid with a feature similar to staggered grid in android as given in this link:
https://dzone.com/articles/how-implement-staggered-grid
Is there a method to do this in WP8.1 programming?
In xaml Use ItemsControl that Represents a control that can be used to present a collection of items.
Create a new Panel that we can use for any ItemsControl. for more information refer this link: http://www.visuallylocated.com/post/2015/02/20/Creating-a-WrapPanel-for-your-Windows-Runtime-apps.aspx
write a class in xaml.cs as follows.
public class WrapPanel : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
// Just take up all of the width
Size finalSize = new Size { Width = availableSize.Width };
double x = 0;
double rowHeight = 0d;
foreach (var child in Children)
{
// Tell the child control to determine the size needed
child.Measure(availableSize);
x += child.DesiredSize.Width;
if (x > availableSize.Width)
{
// this item will start the next row
x = child.DesiredSize.Width;
// adjust the height of the panel
finalSize.Height += rowHeight;
rowHeight = child.DesiredSize.Height;
}
else
{
// Get the tallest item
rowHeight = Math.Max(child.DesiredSize.Height, rowHeight);
}
}
// Just in case we only had one row
if (finalSize.Height == 0)
{
finalSize.Height = rowHeight;
}
return finalSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
Rect finalRect = new Rect(0, 0, (finalSize.Width / 2) - 10, finalSize.Height);
double EvenItemHeight = 0;
double OddItemHeight = 0;
int itemNumber = 1;
foreach (var child in Children)
{
if (itemNumber % 2 == 0)
{
finalRect.X = (finalSize.Width / 2);
finalRect.Y = EvenItemHeight;
EvenItemHeight += Children[itemNumber - 1].DesiredSize.Height;
}
else
{
finalRect.X = 0;
finalRect.Y = OddItemHeight;
OddItemHeight += Children[itemNumber - 1].DesiredSize.Height;
}
itemNumber++;
child.Arrange(finalRect);
}
return finalSize;
}
}
StaggerGrid.xaml code is as follows:
xmlns:local="using:StaggerGridSample.Views"// namespace of class WrapPanel
<Grid>
<ScrollViewer >
<ItemsControl x:Name="ItemsControl" ItemsSource="{Binding StrList,UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Background="Red"
Width="185"
VerticalAlignment="Top"
Margin="0,0,6,0">
<TextBlock Text="{Binding}"
VerticalAlignment="Top"
TextWrapping="Wrap"
FontSize="20"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
I am creating a vertical scrolling game that utilizes a canvas. Though there is no performance issues just yet I am anticipating that there will be since I don't believe the canvas inherently offers virtualization. Is there such thing as a VirtualCanvas similar to the VirtualStackPanel? I want the same functionality where it only draws what is currently being displayed.
Right now my structure looks like this
<canvas Name="GameCanvas">
<canvas Name="StaticBG">
</canvas>
<canvas Name="DynamIcBG">
</canvas>
<canvas Name="CollidableObjects">
</canvas>
<canvas Name="Hud">
</canvas>
</canvas>
I would like to virtualize the DynamicBG and the CollidableObjects canvases
EDIT:
Can I possibly put all of my stuff inside of a VirtualStackPanel? Would that work?
<Canvas>
<Canvas>
<VirtualizingStackPanel>
<Canvas Name="Collidables">
<TextBlock>HOMES IT WORKS</TextBlock>
</Canvas>
</VirtualizingStackPanel>
</Canvas>
<Canvas>
<VirtualizingStackPanel>
<Canvas Name="DynamicBG">
<TextBlock>IT WORKS HOMES</TextBlock>
</Canvas>
</VirtualizingStackPanel>
</Canvas>
</Canvas>
The Canvas is NOT Virtualized - at least, not in the way you'd typically define Virtualization.
The following LINQPad-ready harness will show this - even if the "bugs" are outside of the window extents, the contained bugs will continue to render.
void Main()
{
var bugCount = 4;
var window = System.Windows.Markup.XamlReader.Parse(someXaml)
as System.Windows.Window;
window.Show();
var canvas = window.FindName("canvas")
as System.Windows.Controls.Canvas;
var bugs = new List<Bug>();
var r = new Random();
if(canvas != null)
{
for(int i=0; i < bugCount; i++)
{
var bug = new Bug();
bug.Height = 50;
bug.Width = 50;
bug.Location = new System.Windows.Point(
r.Next(0, (int)canvas.Width),
r.Next(0, (int)canvas.Height));
canvas.Children.Add(bug);
bugs.Add(bug);
}
}
var dt = new System.Windows.Threading.DispatcherTimer();
dt.Tick += (o,e) => MoveIt(bugs);
dt.Interval = TimeSpan.FromMilliseconds(100);
dt.Start();
}
public void MoveIt(List<Bug> bugs)
{
var r = new Random();
foreach (var bug in bugs)
{
var dir = r.Next(0,4);
switch (dir)
{
case 0:
bug.Location =
new System.Windows.Point(bug.Location.X + 1, bug.Location.Y);
break;
case 1:
bug.Location =
new System.Windows.Point(bug.Location.X - 1, bug.Location.Y);
break;
case 2:
bug.Location =
new System.Windows.Point(bug.Location.X, bug.Location.Y + 1);
break;
case 3:
bug.Location =
new System.Windows.Point(bug.Location.X, bug.Location.Y - 1);
break;
}
}
}
public class Bug : System.Windows.Controls.UserControl
{
private static int _bugCounter = 0;
public Bug()
{
this.BugId = _bugCounter++;
}
public int BugId {get; private set;}
private System.Windows.Point _location;
public System.Windows.Point Location
{
get { return _location; }
set
{
_location = value;
System.Windows.Controls.Canvas.SetLeft(this, value.X);
System.Windows.Controls.Canvas.SetTop(this, value.Y);
InvalidateVisual();
}
}
protected override void OnRender(System.Windows.Media.DrawingContext ctx)
{
base.OnRender(ctx);
Console.WriteLine("Yo, bug #{0} is rendering!", BugId);
ctx.DrawRectangle(
System.Windows.Media.Brushes.Red,
new System.Windows.Media.Pen(System.Windows.Media.Brushes.White, 1),
new System.Windows.Rect(Location, this.RenderSize));
var formattedText = new System.Windows.Media.FormattedText(
this.BugId.ToString(),
System.Globalization.CultureInfo.CurrentCulture,
System.Windows.FlowDirection.LeftToRight,
new System.Windows.Media.Typeface("Arial"),
12,
System.Windows.Media.Brushes.White);
ctx.DrawText(
formattedText,
new System.Windows.Point(Location.X + 10, Location.Y + 10));
}
}
string someXaml =
#"
<Window
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
Width=""320""
Height=""240""
>
<Canvas
x:Name=""canvas""
Width=""640""
Height=""480""
Background=""LightGray""
/>
</Window>
";
The answer to this question is that the Canvas fakes virtualization. If you load a bunch of objects on the canvas and they don't need to be drawn on the screen they are overlooked. I say fakes virtualization because this comes from my own investigation and research. I have yet to find a single article relating to this topic.
If you however were to load 200 images that were 100x100 onto a canvas in various spots both on and off the screen. You will notice that as you move the canvas around you will experience no delay.
I am trying to make Alphabetically searching of records via accessing rest services in windows phone 7.
Design page code..
<controls:PivotItem Header="buddies">
<toolkit:LongListSelector x:Name="BookList" Background="Transparent" IsFlatList="true"
GroupViewOpened="LongListSelector_GroupViewOpened"
GroupViewClosing="LongListSelector_GroupViewClosing">
<toolkit:LongListSelector.GroupItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</toolkit:LongListSelector.GroupItemsPanel>
<toolkit:LongListSelector.GroupItemTemplate>
<DataTemplate>
<Border Background="{Binding Converter={StaticResource GroupBackground}}"
Width="99" Height="99" Margin="6" IsHitTestVisible="{Binding HasItems}">
<TextBlock Text="{Binding Title}"
FontFamily="{StaticResource PhoneFontFamilySemiBold}"
FontSize="48"
Margin="8,0,0,0"
Foreground="{Binding Converter={StaticResource GroupForeground}}"
VerticalAlignment="Bottom"/>
<Border.Projection>
<PlaneProjection RotationX="-60"/>
</Border.Projection>
</Border>
</DataTemplate>
</toolkit:LongListSelector.GroupItemTemplate>
<toolkit:LongListSelector.GroupHeaderTemplate>
<DataTemplate>
<Border Background="Transparent" Margin="12,8,0,8">
<Border Background="{StaticResource PhoneAccentBrush}"
Padding="8,0,0,0" Width="62" Height="62"
HorizontalAlignment="Left">
<TextBlock Text="{Binding Title}"
Foreground="#FFFFFF"
FontSize="48"
FontFamily="{StaticResource PhoneFontFamilySemiLight}"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"/>
</Border>
</Border>
</DataTemplate>
</toolkit:LongListSelector.GroupHeaderTemplate>
<toolkit:LongListSelector.ItemTemplate>
<DataTemplate>
<Grid Margin="12,8,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Width="110" Height="150" Source="{Binding ImageUrl}" VerticalAlignment="Top"/>
<StackPanel Grid.Column="1" VerticalAlignment="Top">
<TextBlock Text="{Binding AutherName}" Style="{StaticResource PhoneTextLargeStyle}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" Margin="12,-12,12,6"/>
<TextBlock Text="{Binding Email}" Style="{StaticResource PhoneTextNormalStyle}" TextWrapping="Wrap" FontFamily="{StaticResource PhoneFontFamilySemiBold}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Title:" Style="{StaticResource PhoneTextSmallStyle}"/>
<TextBlock Text="{Binding Title}" Style="{StaticResource PhoneTextSmallStyle}" FontFamily="{StaticResource PhoneFontFamilySemiBold}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Price:" Style="{StaticResource PhoneTextSmallStyle}"/>
<TextBlock Text="{Binding Price}" Style="{StaticResource PhoneTextSmallStyle}" FontFamily="{StaticResource PhoneFontFamilySemiBold}"/>
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</toolkit:LongListSelector.ItemTemplate>
</toolkit:LongListSelector>
</controls:PivotItem>
</controls:Pivot>
</Grid>
Here is my MainPage.xaml.cs page code
private LongListSelector currentSelector;
List<Person> objperson = null;
// Constructor
public MainPage()
{
InitializeComponent();
string Categoryid = "2";
WebClient proxy = new WebClient();
proxy.DownloadStringAsync(new Uri("http://localhost:3160/Service1.svc/GetListItemDetail/" + Categoryid));
proxy.DownloadStringCompleted += new DownloadStringCompletedEventHandler(proxy_DownloadStringCompleted);
}
void proxy_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
XDocument doc = XDocument.Load(new StringReader(e.Result));
var CatList = (from item in doc.Descendants("ItemDetail")
select new Person
{
GenreName = item.Element("GenreName").Value.ToString(),
ItemID = Convert.ToInt32(item.Element("ItemID").Value),
CatID = Convert.ToInt32(item.Element("CatID").Value),
GenreID = Convert.ToInt32(item.Element("GenreID").Value),
AutherName = item.Element("AutherName").Value.ToString(),
Title = item.Element("Title").Value.ToString(),
Email = item.Element("Email").Value.ToString(),
Price = item.Element("Price").Value.ToString(),
Description = item.Element("Description").Value.ToString(),
ImageUrl = item.Element("ImageUrl").Value.ToString()
}).ToList();
objperson = new List<Person>();
objperson = CatList;
BookList.ItemsSource = CatList;
}
}
public List<Person> GetPersonListInfo()
{
List<Person> objp = new List<Person>();
objp = objperson;
return objp;
}
private void LongListSelector_GroupViewOpened(object sender, GroupViewOpenedEventArgs e)
{
//Hold a reference to the active long list selector.
currentSelector = sender as LongListSelector;
//Construct and begin a swivel animation to pop in the group view.
IEasingFunction quadraticEase = new QuadraticEase { EasingMode = EasingMode.EaseOut };
Storyboard _swivelShow = new Storyboard();
ItemsControl groupItems = e.ItemsControl;
foreach (var item in groupItems.Items)
{
UIElement container = groupItems.ItemContainerGenerator.ContainerFromItem(item) as UIElement;
if (container != null)
{
Border content = VisualTreeHelper.GetChild(container, 0) as Border;
if (content != null)
{
DoubleAnimationUsingKeyFrames showAnimation = new DoubleAnimationUsingKeyFrames();
EasingDoubleKeyFrame showKeyFrame1 = new EasingDoubleKeyFrame();
showKeyFrame1.KeyTime = TimeSpan.FromMilliseconds(0);
showKeyFrame1.Value = -60;
showKeyFrame1.EasingFunction = quadraticEase;
EasingDoubleKeyFrame showKeyFrame2 = new EasingDoubleKeyFrame();
showKeyFrame2.KeyTime = TimeSpan.FromMilliseconds(85);
showKeyFrame2.Value = 0;
showKeyFrame2.EasingFunction = quadraticEase;
showAnimation.KeyFrames.Add(showKeyFrame1);
showAnimation.KeyFrames.Add(showKeyFrame2);
Storyboard.SetTargetProperty(showAnimation, new PropertyPath(PlaneProjection.RotationXProperty));
Storyboard.SetTarget(showAnimation, content.Projection);
_swivelShow.Children.Add(showAnimation);
}
}
}
_swivelShow.Begin();
}
private void LongListSelector_GroupViewClosing(object sender, GroupViewClosingEventArgs e)
{
//Cancelling automatic closing and scrolling to do it manually.
e.Cancel = true;
if (e.SelectedGroup != null)
{
currentSelector.ScrollToGroup(e.SelectedGroup);
}
//Dispatch the swivel animation for performance on the UI thread.
Dispatcher.BeginInvoke(() =>
{
//Construct and begin a swivel animation to pop out the group view.
IEasingFunction quadraticEase = new QuadraticEase { EasingMode = EasingMode.EaseOut };
Storyboard _swivelHide = new Storyboard();
ItemsControl groupItems = e.ItemsControl;
foreach (var item in groupItems.Items)
{
UIElement container = groupItems.ItemContainerGenerator.ContainerFromItem(item) as UIElement;
if (container != null)
{
Border content = VisualTreeHelper.GetChild(container, 0) as Border;
if (content != null)
{
DoubleAnimationUsingKeyFrames showAnimation = new DoubleAnimationUsingKeyFrames();
EasingDoubleKeyFrame showKeyFrame1 = new EasingDoubleKeyFrame();
showKeyFrame1.KeyTime = TimeSpan.FromMilliseconds(0);
showKeyFrame1.Value = 0;
showKeyFrame1.EasingFunction = quadraticEase;
EasingDoubleKeyFrame showKeyFrame2 = new EasingDoubleKeyFrame();
showKeyFrame2.KeyTime = TimeSpan.FromMilliseconds(125);
showKeyFrame2.Value = 90;
showKeyFrame2.EasingFunction = quadraticEase;
showAnimation.KeyFrames.Add(showKeyFrame1);
showAnimation.KeyFrames.Add(showKeyFrame2);
Storyboard.SetTargetProperty(showAnimation, new PropertyPath(PlaneProjection.RotationXProperty));
Storyboard.SetTarget(showAnimation, content.Projection);
_swivelHide.Children.Add(showAnimation);
}
}
}
_swivelHide.Completed += _swivelHide_Completed;
_swivelHide.Begin();
});
}
private void _swivelHide_Completed(object sender, EventArgs e)
{
//Close group view.
if (currentSelector != null)
{
currentSelector.CloseGroupView();
currentSelector = null;
}
}
I am new to windows phone 7 application development, no idea about grouping of alphabets in Longlistselector. Please help me in that. Thanks in advance.
There is code I used once for grouping. As you can see, it's similar to Claus's:
public class YourList : ObservableCollection<ItemsInGroup>
{
private static readonly string Groups = "#abcdefghijklmnopqrstuvwxyz";
Dictionary<string, ItemsInGroup> groups = new Dictionary<string, ItemsInGroup>();
public YourList()
{
foreach (char c in Groups)
{
ItemsInGroup group = new ItemsInGroup(c.ToString());
this.Add(group);
groups[c.ToString()] = group;
}
}
public void AddItem(Item item)
{
string GroupKey = Item.GetSomeFieldKey(item);// a, b, etc.
for (int i = 0; i < groups[GroupKey].Count; i++)
{
if (Item.CompareBySomeField(item, groups[GroupKey][i]) < 0)
{
groups[Item.GetSomeFilesKey(item)].Insert(i, item);
return;
}
}
groups[GroupKey].Add(item);
}
}
.
public class ItemsInGroup : ObservableCollection<Item>, INotifyPropertyChanged
{
public ItemsInGroup(string category)
{
Key = category;
}
public string Key { get; set; }
public bool HasItems { get { return Count > 0; } }
//INotifyPropertyChanged implementation
}
Item must implement:
public static string GetSomeFieldKey(Item item)
and
public static int CompareBySomeFields(object obj1, object obj2)
Usage:
YourList list = new YourList();
foreach (var item in resultListFromService)
{
list.AddItem(item); // fill list with items
}
myList.ItemsSource = list; // bind to UI
Hope this helps better understand how it works
A super easy way to do it, is to use a specialized collection for the LongListSelector. I just so happen to have written one
Basically you would change your code to the following:
BookList.ItemsSource = new LongListCollection<Person, char>(CatList, x => x.Title[0]));
And you would get the alphabetic grouping on the first character of the Title property.
The only detail you need to be aware of, is that your Person class would need to implement IComparable<Person> to be ordered by the Title property (because you do want sorting, right?)
Simply done as:
public int Compare(Person other)
{
if (other == null)
return 1;
return this.Title.CompareTo(other.Title);
}