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.
Related
There is a Grid, which is filled dynamically with Image controls in code behind(Sorry for that).
Grid has 1 column, many pages, each page has 1 Border with Image as Border.Child inside. What I need is to Zoom (Scale) my Image in Grid when Button.Click event fires. I used Scale Transform with the Image before, but I didn't manage to bind Grid element Image with the Click handler.
Please suggest, how I can zoom images inside grid, step by step.
Thanks in advance!
Yes, I know this is horrible, should be done in different way, I'm still learning, how to do this right.
Method, that generates Grid. After that ZOOM click method ( only for zoom, there is another method for zoom out)
public void RefreshView(List<TiffImage> tiffImageList)
{
try
{
if (tiffImageList.Count == 0)
return;
SetControlSizes();
gridImageList.Children.Clear();
gridImageList.RowDefinitions.Clear();
gridImageList.ColumnDefinitions.Clear();
RowDefinitionCollection rd = gridImageList.RowDefinitions;
ColumnDefinitionCollection cd = gridImageList.ColumnDefinitions;
cd.Add(new ColumnDefinition() { Width = GridLength.Auto });
for (int i = 0; i < tiffImageList.Count; i++)
{
rd.Add(new RowDefinition() { Height = GridLength.Auto });
}
int rowIndex = 0;
foreach (var tiffImage in tiffImageList)
{
Image imageListViewItem = new Image();
imageListViewItem.Margin = new Thickness(0, 0, 0, 0);
RenderOptions.SetBitmapScalingMode(imageListViewItem, BitmapScalingMode.HighQuality);
imageListViewItem.Name = $"Image{tiffImage.index.ToString()}";
imageListViewItem.Source = tiffImage.image;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.Stretch = Stretch.Uniform;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
Border border = new Border();
border.BorderBrush = Brushes.LightGray;
border.BorderThickness = new Thickness(1);
Thickness margin = border.Margin;
border.Margin = new Thickness(20, 10, 20, 10);
border.Child = imageListViewItem;
Grid.SetColumn(border, 0);
Grid.SetRow(border, rowIndex);
gridImageList.Children.Add(border);
rowIndex++;
}
}
catch (Exception ex)
{
throw ex;
}
}
private void btnZoom_Click(object sender, RoutedEventArgs e)
{
foreach (UIElement item in gridImageList.Children)
{
Border border = (Border)item;
Image image = (Image)border.Child;
var imgViewerScaleTransform = (ScaleTransform)(image.LayoutTransform);
if ((imgViewerScaleTransform.ScaleX + 0.2) > 3 || (imgViewerScaleTransform.ScaleY + 0.2) > 3)
return;
imgViewerScaleTransform.ScaleX += 0.2;
imgViewerScaleTransform.ScaleY += 0.2;
image.LayoutTransform = imgViewerScaleTransform;
}
}
Here is a very simple version of a scalable ItemsControl in a ScrollViewer.
It might be improved in many ways. First of all, you should replace handling Button Click events by binding the Button Command properties to ZoomIn and ZoomOut commands in the view model (left out for brevity).
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Images}">
<ItemsControl.LayoutTransform>
<ScaleTransform ScaleX="{Binding Scale}" ScaleY="{Binding Scale}"/>
</ItemsControl.LayoutTransform>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="LightGray">
<Image Source="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content=" + " Click="ZoomInButtonClick"/>
<Button Content=" - " Click="ZoomOutButtonClick"/>
</StackPanel>
</Grid>
The code behind:
public partial class MainWindow : Window
{
private readonly ViewModel viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = viewModel;
foreach (string imageFile in Directory.EnumerateFiles(
#"C:\Users\Public\Pictures\Sample Pictures", "*.jpg"))
{
viewModel.Images.Add(new BitmapImage(new Uri(imageFile)));
}
}
private void ZoomInButtonClick(object sender, RoutedEventArgs e)
{
viewModel.Scale *= 1.1;
}
private void ZoomOutButtonClick(object sender, RoutedEventArgs e)
{
viewModel.Scale /= 1.1;
}
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ImageSource> Images { get; }
= new ObservableCollection<ImageSource>();
private double scale = 1;
public double Scale
{
get { return scale; }
set
{
scale = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Scale)));
}
}
}
I managed to find an ugly, horrible solution, sorry for that. Use it only, if there is no any alternative. Please, add answers with better solutions. Thanks for your time!
We need to add (in code behind) Image.LayoutTransform defined as ScaleTransform:
imageListViewItem.LayoutTransform = new ScaleTransform();
I used // to emphasize changes in method below. Also, most changes happened in Zoom/ZoomOut methods below.
public void RefreshView(List<TiffImage> tiffImageList)
{
try
{
if (tiffImageList.Count == 0)
return;
SetControlSizes();
gridImageList.Children.Clear();
gridImageList.RowDefinitions.Clear();
gridImageList.ColumnDefinitions.Clear();
RowDefinitionCollection rd = gridImageList.RowDefinitions;
ColumnDefinitionCollection cd = gridImageList.ColumnDefinitions;
cd.Add(new ColumnDefinition() { Width = GridLength.Auto });
for (int i = 0; i < tiffImageList.Count; i++)
{
rd.Add(new RowDefinition() { Height = GridLength.Auto });
}
int rowIndex = 0;
foreach (var tiffImage in tiffImageList)
{
Image imageListViewItem = new Image();
imageListViewItem.Margin = new Thickness(0, 0, 0, 0);
RenderOptions.SetBitmapScalingMode(imageListViewItem, BitmapScalingMode.HighQuality);
imageListViewItem.Name = $"Image{tiffImage.index.ToString()}";
imageListViewItem.Source = tiffImage.image;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.Stretch = Stretch.Uniform;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
// Add HERE!!!
imageListViewItem.LayoutTransform = new ScaleTransform();
//
Border border = new Border();
border.BorderBrush = Brushes.LightGray;
border.BorderThickness = new Thickness(1);
Thickness margin = border.Margin;
border.Margin = new Thickness(20, 10, 20, 10);
border.Child = imageListViewItem;
Grid.SetColumn(border, 0);
Grid.SetRow(border, rowIndex);
gridImageList.Children.Add(border);
rowIndex++;
}
}
catch (Exception ex)
{
throw ex;
}
}
We take all elements from the Grid and Scale(Zoom) them, then we clear the Grid.Children and fill it with new Items.
private void btnZoom_Click(object sender, RoutedEventArgs e)
{
List<Border> list = new List<Border>();
foreach (UIElement item in gridImageList.Children)
{
Border border = (Border)item;
Image image = (Image)border.Child;
var imgViewerScaleTransform = (ScaleTransform)(image.LayoutTransform);
imgViewerScaleTransform.CenterX = 0.5;
imgViewerScaleTransform.CenterY = 0.5;
if ((imgViewerScaleTransform.ScaleX + 0.2) > 3 || (imgViewerScaleTransform.ScaleY + 0.2) > 3)
return;
imgViewerScaleTransform.ScaleX += 0.2;
imgViewerScaleTransform.ScaleY += 0.2;
image.LayoutTransform = imgViewerScaleTransform;
border.Child = image;
list.Add(border);
}
gridImageList.Children.Clear();
foreach (Border border in list)
{
gridImageList.Children.Add(border);
}
}
private void btnZoomOut_Click(object sender, RoutedEventArgs e)
{
List<Border> list = new List<Border>();
foreach (UIElement item in gridImageList.Children)
{
Border border = (Border)item;
Image image = (Image)border.Child;
var imgViewerScaleTransform = (ScaleTransform)(image.LayoutTransform);
imgViewerScaleTransform.CenterX = 0.5;
imgViewerScaleTransform.CenterY = 0.5;
if ((imgViewerScaleTransform.ScaleX - 0.2) < 0.8 || (imgViewerScaleTransform.ScaleY - 0.2) < 0.8)
return;
imgViewerScaleTransform.ScaleX += -0.2;
imgViewerScaleTransform.ScaleY += -0.2;
image.LayoutTransform = imgViewerScaleTransform;
border.Child = image;
list.Add(border);
}
gridImageList.Children.Clear();
foreach (Border border in list)
{
gridImageList.Children.Add(border);
}
}
I am trying to rotate an image on an EventHandler, for an Event that i created. The Event Handler works perfectly fine. Yet the image does not rotate and I do not know what I am missing.
This UserControl is not displayed like this, but in a Grid of another UserControl.
<UserControl x:Class="Mabri.Module.P83.View.SchematicSystemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Mabri.Module.P83.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Image x:Name="drehteller" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</UserControl>
This is the code behind the XAML, at first I tried having this in a separat class, if this is possible it would be even better, but at the moment I am just trying to get this running.
{
private System.Windows.Controls.Image drehteller_image;
private int currentAngle = 0;
public SchematicSystemView()
{
EventAggregator.SubscribeEvent(this);
InitializeComponent();
drehteller_image = new System.Windows.Controls.Image()
{
Stretch = Stretch.Uniform,
Source = new BitmapImage(new Uri("pack://application:,,,/Mabri.Module.P83;Component/Images/drehteller_unbestueckt.png")),
RenderTransform = new RotateTransform()
};
drehteller.Source = drehteller_image.Source;
}
public void OnEventHandler(DrehtellerMoved e)
{
EventAggregator.PublishEvent(new LogInfo
{
Title = "Should rotate",
Summary = "The drehteller should rotate now",
Detail = "The drehteller should rotate " + currentAngle + " is the current angle",
LogLevel = LogLevel.Information
});
int steps = e.steps;
double timeforonestep = e.speed / e.steps;
int angle = steps * 72;
int angle_to_reach = currentAngle + angle;
Storyboard storyboard = new Storyboard();
storyboard.Duration = new Duration(TimeSpan.FromSeconds(timeforonestep * steps));
DoubleAnimation rotateAnimation = new DoubleAnimation()
{
From = currentAngle,
To = currentAngle + angle,
Duration = storyboard.Duration
};
Storyboard.SetTarget(rotateAnimation, drehteller);
Storyboard.SetTargetProperty(rotateAnimation, new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)"));
storyboard.Children.Add(rotateAnimation);
storyboard.Begin();
currentAngle = angle_to_reach;
}
}
In the testing case e.steps equals 1 and e.speed equals 10.0.
I expected the image to turn for 72 degrees, but for some reasons nothing happens, except for the LogMessage at the beginning of the handler.
First, assign a RotateTransform to the RenderTransform property of the Image. Otherwise it can't be animated.
<Image x:Name="drehteller" HorizontalAlignment="Center" VerticalAlignment="Center"
RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<RotateTransform/>
</Image.RenderTransform>
</Image>
Then instead of setting From and To just set By. And drop the Storyboard and start the animation directly on the control's RenderTransform
var animation = new DoubleAnimation
{
By = angle,
Duration = TimeSpan.FromSeconds(...)
};
drehteller.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, animation);
Finally, do not create the intermediate Image element. That is redundant.
drehteller.Source = new BitmapImage(new Uri("..."));
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...
In its simplest form...
I would like to create as many StackPanels as I want and then add Rectangles in them. Then to be able to change the Fill color of any one of the Rectangles when I click the Start Button for instance. All in Code Behind.
Any help would be appreciated.
For example, if our favorite beer wrote the framework I could do it like this:
XAML:
<Page
x:Class="Test2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Test2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Name="StartButton" Content="Start" Click="StartButton_Click" Height="30" Width="200" Margin="5"/>
</StackPanel>
<StackPanel Grid.Row="1" Name="myStackPanel" VerticalAlignment="Top"/>
</Grid>
</Page>
Code Behind:
namespace Test2
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
for (var i = 0; i < 5; i++) // The 5 here could be any number
{
myStackPanel.Children.Add(new StackPanel
{
Name = "myPanel" + i,
Orientation = Orientation.Horizontal
});
for (var j = 0; j < 10; j++) // The 10 here could be any number
{
("myPanel" + i).Children.Add(new Rectangle
{
Name = "myRectangle" + i + "-" + j,
Fill = new SolidColorBrush(Colors.Black),
Width = 20,
Height = 20,
Margin = new Thickness(1)
});
}
}
}
private void StartButton_Click(object sender, RoutedEventArgs e)
{
// E.G. To change the Fill color of Rectangle4 in StackPanel2
("myRectangle" + 2 + "-" + 4).Fill = new SolidColorBrush(Colors.Red);
}
}
}
Firstly, to add Rectangle shapes, we can create an instance of StackPanel and manipulate its Children elements:
for (var i = 0; i < 5; i++) // The 5 here could be any number
{
StackPanel sp = new StackPanel
{
Name = "myPanel" + i,
Orientation = Orientation.Horizontal
};
myStackPanel.Children.Add(sp);
for (var j = 0; j < 10; j++) // The 10 here could be any number
{
sp.Children.Add(new Rectangle
{
Name = "myRectangle" + i + "-" + j,
Fill = new SolidColorBrush(Colors.Black),
Width = 20,
Height = 20,
Margin = new Thickness(1)
});
}
}
Then to be able to change the Fill color of any one of the Rectangles when I click the Start Button for instance. All in Code Behind.
As tgpdyk mentioned, we need to use VisualTreeHelper to find the specified rectangle shape.
Helper class:
public static class FrameworkElementExtensions
{
public static T TraverseCTFindShape<T>(DependencyObject root, String name) where T : Windows.UI.Xaml.Shapes.Shape
{
T control = null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++)
{
var child = VisualTreeHelper.GetChild(root, i);
string childName = child.GetValue(FrameworkElement.NameProperty) as string;
control = child as T;
if (childName == name)
{
return control;
}
else
{
control = TraverseCTFindShape<T>(child, name);
if (control != null)
{
return control;
}
}
}
return control;
}
}
How to use it:
private void StartButton_Click(object sender, RoutedEventArgs e)
{
// E.G. To change the Fill color of Rectangle4 in StackPanel2
var rec = FrameworkElementExtensions.TraverseCTFindShape<Shape>(myStackPanel, "myRectangle" + 2 + "-" + 4);
rec.Fill = new SolidColorBrush(Colors.Red);
}
I've uploaded my sample to Github repository
That is not how you approach this in WPF, at all.
You usually do not concern yourself with any UI components but only the data. In this case you data bind an ItemsControl to a list of rows, each row containing a list of cells. In the ItemsControl definition you then set an ItemTemplate that contains another ItemsControl binding to the cells. In the nested ItemsControl you then can set an ItemTemplate where you bind the Background to a (notifying) property of your cells which you then just need to change in code.
Check out these overviews:
Data Binding Overview
Data Templating Overview
You may also want to look into the Model-View-ViewModel pattern to ensure a suitable application architecture.
I am wondering on the best way of dealing with a problem I have.
I am in the early stages developing a WPF application that has several images that are all
individual classes. I am attempting to use the MVVM model but I wish to implement a Mouse over event such that when I scroll over a image I wish to bring up some information or do some effects over the image I am not quite sure how to go around this but I have an example of the class below.
public class TeamKit
{
private Image mainImage;
public Canvas Position { get; set; }
public TextBlock PlayerText { get; set; }
public TextBlock NameText{ get; set; }
public Player Player { get; set; }
private string _playerName = "Player0";
private string _playerNumber = "0";
private BitmapImage _bImage;
public TeamKit(Thickness t)
{
mainImage = new Image();
_bImage = new BitmapImage(new Uri("pack://Application:,,,/Resources/KitC.png"));
mainImage.Source = _bImage;
mainImage.Stretch = System.Windows.Media.Stretch.None;
Position = new Canvas();
Position.Width = 38;
Position.Height = 45;
Position.Margin = t;
mainImage.Margin = new Thickness(0, 0, 0, 6);
PlayerText = new TextBlock();
PlayerText.Text = ""; PlayerText.TextAlignment = TextAlignment.Center;
PlayerText.Margin = new Thickness(11, 15, 27, 15);
Position.Children.Add(mainImage);
Position.Children.Add(PlayerText);
}
public TeamKit()
{
mainImage = new Image();
_bImage = new BitmapImage(new Uri("pack://Application:,,,/Resources/KitC.png"));
mainImage.Source = _bImage;
mainImage.Stretch = System.Windows.Media.Stretch.None;
Position = new Canvas();
Position.Width = 38;
Position.Height = 45;
//mainImage.Margin = new Thickness(0, 0, 0, 6);
PlayerText = new TextBlock();
PlayerText.Text = _playerNumber; PlayerText.TextAlignment = TextAlignment.Center;
PlayerText.Margin = new Thickness(12, 15, 27, 15);
PlayerText.Width = 15;
Viewbox Vb = new Viewbox();
//Vb.StretchDirection = StretchDirection.Both;
Vb.Stretch = System.Windows.Media.Stretch.Uniform;
Vb.VerticalAlignment = VerticalAlignment.Stretch;
Canvas.SetTop(Vb, 40);
Canvas.SetLeft(Vb, -11);
Vb.MaxWidth = 50;
NameText = new TextBlock();
NameText.Text = _playerName;
NameText.TextAlignment = TextAlignment.Center;
NameText.MaxHeight = 40;
NameText.MaxWidth = 90;
Vb.Child = NameText;
Position.Children.Add(Vb);
//<TextBlock Text="FooAlanghi" TextWrapping="Wrap" TextAlignment="Center" MaxHeight="40" MaxWidth="92" />
Position.Children.Add(mainImage);
Position.Children.Add(PlayerText);
}
public TeamKit(Player Player, int PlayerNumber)
{
this.Player = Player;
_playerNumber = PlayerNumber.ToString();
_playerName = Player.Last_Name;
mainImage = new Image();
_bImage = new BitmapImage(new Uri("pack://Application:,,,/Resources/KitC.png"));
mainImage.Source = _bImage;
mainImage.Stretch = System.Windows.Media.Stretch.None;
Position = new Canvas();
Position.Width = 38;
Position.Height = 45;
//mainImage.Margin = new Thickness(0, 0, 0, 6);
PlayerText = new TextBlock();
PlayerText.Text = _playerNumber; PlayerText.TextAlignment = TextAlignment.Center;
PlayerText.Margin = new Thickness(12, 15, 27, 15);
PlayerText.Width = 15;
Viewbox Vb = new Viewbox();
//Vb.StretchDirection = StretchDirection.Both;
Vb.Stretch = System.Windows.Media.Stretch.Uniform;
Vb.VerticalAlignment = VerticalAlignment.Stretch;
Canvas.SetTop(Vb, 40);
Canvas.SetLeft(Vb, -11);
Vb.MaxWidth = 50;
NameText = new TextBlock();
NameText.Text = _playerName;
NameText.TextAlignment = TextAlignment.Center;
NameText.MaxHeight = 40;
NameText.MaxWidth = 90;
Vb.Child = NameText;
Position.Children.Add(Vb);
//<TextBlock Text="FooAlanghi" TextWrapping="Wrap" TextAlignment="Center" MaxHeight="40" MaxWidth="92" />
Position.Children.Add(mainImage);
Position.Children.Add(PlayerText);
}
public void Add(Panel Parent)
{
Parent.Children.Add(this.Position);
}
public static void DrawPositionLineUp(List<TeamKit> Players, Panel panel, double top, double left)
{
double ix = 0;
foreach (TeamKit t in Players)
{
Canvas.SetLeft(t.Position, left);
Canvas.SetTop(t.Position, ix += top);
t.Add(panel);
}
}
}
It's nice that you're using the MVVM route, but be aware that what you are wanting to do has nothing to do with MVVM. You're question is all view-related (well, mostly).
You're control needs a ToolTip:
<Image ...>
<Image.ToolTip>
<ToolTip ...>
Content (which can be another layout of controls)
</ToolTip>
</Image.ToolTip>
</Image>
As for effects, depending on what effects, a lot of them are already built in (blur, shadow, etc). But, either way you want to use Triggers within the style.
<Image ...>
<Image.Style>
<Style>
<Trigger Property="Image.IsMouseOver" Value="True">
<Setter ... /> <!-- Apply Styles Here -->
</Trigger>
</Style>
</Image.Style>
</Image>
NOTE: It's probably best to pull out the style into a static resource and apply it to all controls of that kind within that Window/User Control/Page or higher up in the chain (Application) so that you can do...
<Image Style="{StaticResource MouseOverImage}" ... />
As for why I said "you're question is all view-related (well, mostly)" ... what I mean is that it is view-related up until the point of databinding, then you must coordinate with your view-model to make sure it exposes properties that you need. Outside of that, it is a 100% view-related question and you do not have to worry about any MVVMness in this situation. Continue using MVVM, but realize that this is how you'd do it whether or not you were using MVVM.
Have you considered using triggers?
When handling mouseover events I usually go with triggers, but you should also consider MVVM light - (or something similar) its a great tool for MVVM enthusiasts.