The wpf controls dose't displaying on the canvas - c#

I inheritance from a canvas control and I create my custom canvas class like this:
public class MyCanvas:Canvas
{
//this list will contains all shape
VisualCollection graphicsList;
List<GraphicsBase> cloneGraphicsList;
int c = 0;
double deltaX = 0;
double deltaY = 0;
public MyCanvas()
:base()
{
graphicsList = new VisualCollection(this);
cloneGraphicsList = new List<GraphicsBase>();
}
public VisualCollection GraphicsList
{
get
{
return graphicsList;
}
set
{
graphicsList = value;
}
}
protected override int VisualChildrenCount
{
get
{
return graphicsList.Count;
}
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= graphicsList.Count)
{
throw new ArgumentOutOfRangeException("index");
}
return graphicsList[index];
}
public GraphicsBase this[int index]
{
get
{
if (index >= 0 && index < GraphicsList.Count)
{
return (GraphicsBase)GraphicsList[index];
}
return null;
}
}
public int Count
{
get
{
return GraphicsList.Count;
}
}
}
and in XAML use this code:
<Window x:Class="MyNameSpace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CustomCanvas="clr-namespace:MyNameSpace"
xmlns:WPFRuler="clr-namespace:Orbifold.WPFRuler;assembly=Orbifold.WPFRuler"
Title="PrintVarsDesigner" Height="709" Width="964"
Background="LightGray" Grid.IsSharedSizeScope="False" OverridesDefaultStyle="False"
WindowState="Maximized" WindowStartupLocation="CenterScreen">
<CustomCanvas:MyCanvas x:Name="myCanvas" Background="White" VerticalAlignment="Top"
Width="895" Height="1162">
</CustomCanvas:MyCanvas>
</Window>
the controls does't appear after add its from visual screen or C# code by add child to canvas.
Thanks for any advice.

Inheriting WPF controls is problematic to say the least. WPF control's are "lookless". that is to say, the control itself doesn't know how it's going to be presented. When the control is placed in a window WPF looks for the corresponding ControlTemplate to this specific control.
The problem with inherited controls is that they have no such template. If you want to display it, you'll have to write one yourself, and that's not always simple. You can find an example here, but i'd recommend against it. Use UserControls instead.

Related

How to create binding for FrameworkElement VisualCollection

I need to draw some charts in WPF with hit testing capability. Following the instructions in the docs I'm drawing the geometries as DrawingVisuals and have a host container implemented for them like this (skipping the hit testing code for brevity):
public class MyVisualHost : FrameworkElement
{
public VisualCollection children;
public MyVisualHost()
{
children = new VisualCollection(this);
}
protected override int VisualChildrenCount
{
get { return children.Count; }
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= children.Count)
throw new ArgumentOutOfRangeException();
return children[index];
}
}
and use it in xaml like this
<local:MyVisualHost/>
The user can zoom and scroll the charts, and the DrawingVisuals get updated in a separate thread not to block the UI.
How do I define binding for the children property, so that it was possible to alter it (update DrawingVisuals contained in it) at runtime?
Update
I have just noticed that when you select the xaml element in the xaml editor there is VisualCollection property listed in the Properties panel.
I've tried defining binding for it, but it says:
A 'Binding' cannot be set on the 'VisualCollection' property of type
'...MyVisualHost...'. A 'Binding' can only be set on a
DependencyProperty of a DependencyObject
First of all you will need some public properties on your container...
public class MyVisualHost : FrameworkElement
{
public VisualCollection children;
public MyVisualHost()
{
children = new VisualCollection(this);
children.Add(new Button() {Name = "button"});
children.Add(new TextBox() {Name = "textbox"});
}
protected override int VisualChildrenCount
{
get { return children.Count; }
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= children.Count)
throw new ArgumentOutOfRangeException();
return children[index];
}
public int Count => VisualChildrenCount;
public Visual this[int index]
{
get { return GetVisualChild(index); }
}
}
Next define it in XAML like this...
<Window.Resources>
<local:MyVisualHost x:Key="MyVisualHost"/>
</Window.Resources>
Finally bind to the properties like this...
<TextBox Text="{Binding Path=Count, Source={StaticResource MyVisualHost}, Mode=OneWay}"/>
<TextBox Text="{Binding Path=[0].Name, Source={StaticResource MyVisualHost}, Mode=OneWay}"/>

How to bind a boolean array to the visibility properties of a grid of rectangles in WPF?

I'm trying to make a simple snake game in WPF using a grid of rectangles. I have the snake logic finished more or less, but I'm stumped on how to actually visualise my two dimensional array of booleans as pixels(rectangles) being visible or not. This question addresses how to treat visibility as a boolean, but how would I scale this up?
The code below describes the snake moving, I aim to run its Update method in a separate thread. It works by adding SnakeBod objects to a list, which have an age property. When the Age is over a certain number, the Update method stops treating it as existing. I aim to control the snake with KeyDown events.
The Question is: how do I bind the two dimensional VisibleBods array to a 64x64 grid in my MainWindow?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace SnakeGame
{
static class Snake
{
static public int SnakeLife;
static public List<SnakeBod> SnakeBods;
static SnakePos headPos;
static public bool[,] VisibleBods = new bool[64, 64];
internal static SnakePos HeadPos
{
get => headPos;
set
{
if (headPos.X != value.X || headPos.Y != value.Y)
{
if (headPos.X > 64 || headPos.X < 0 || headPos.Y > 64 || headPos.X < 0) SnakeLife = -1;
bool exists = false;
headPos = value;
foreach (SnakeBod curBod in SnakeBods)
{
if (curBod.BodPos.X == value.X && curBod.BodPos.Y == value.Y)
{
exists = true;
if (curBod.age > SnakeLife) curBod.age = 0;
else SnakeLife = -1;
}
}
if (!exists) SnakeBods.Add(new SnakeBod(value.X, value.Y));
}
}
}
static int snakeWait;
static Direction SnakeDir;
enum Direction : int
{
LEFT = 0,
UP = 1,
RIGHT = 2,
DOWN = 3,
}
static Snake()
{
headPos = new SnakePos(32, 32);
for (int i = 0; i < 64; i++)
{
for (int j = 0; j < 64; j++)
{
VisibleBods[i, j] = false;
}
}
SnakeLife = 10;
}
static void UpdateBod()
{
for (int i = 0; i < 64; i++)
{
for (int j = 0; j < 64; j++)
{
VisibleBods[i, j] = false;
}
}
foreach (SnakeBod curBod in SnakeBods)
{
if (curBod.age < SnakeLife) VisibleBods[curBod.BodPos.X, curBod.BodPos.Y] = true;
}
}
static Thread UpdateThread;
static void UpdateSnake()
{
Thread.CurrentThread.IsBackground = true;
while (SnakeLife > 0)
{
switch (SnakeDir)
{
case Direction.LEFT:
HeadPos = new SnakePos(HeadPos.X - 1, HeadPos.Y);
break;
case Direction.UP:
HeadPos = new SnakePos(HeadPos.X, HeadPos.Y - 1);
break;
case Direction.RIGHT:
HeadPos = new SnakePos(HeadPos.X + 1, HeadPos.Y);
break;
case Direction.DOWN:
HeadPos = new SnakePos(HeadPos.X, HeadPos.Y + 1);
break;
}
foreach (SnakeBod curBod in SnakeBods)
{
curBod.age++;
}
Thread.Sleep(snakeWait);
}
}
}
class Crumb
{
}
class SnakeBod
{
public SnakePos BodPos;
public int age;
public SnakeBod(int xIn, int yIn)
{
age = 0;
BodPos.X = xIn;
BodPos.Y = yIn;
}
}
internal struct SnakePos
{
public int X;
public int Y;
public SnakePos(int xIn, int yIn)
{
X = xIn;
Y = yIn;
}
}
}
EDIT: I am also aware of this codeproject, but I wanted to try this in my own way. Usually I do Bindings mostly in XAML, and since this is large scale I figured this has to be done in the Codebehind. One of the biggest hurdles is how to bind to an array element inside the ViewModel, as for normal fields I just call OnPropertyChanged() when set.
In order to update the UI on a visibility change of a single cell in a grid of cells, you should implement the standard MVVM approach, i.e. bind an ItemsControl to a collection of cell items, with an item class that implements INotifyPropertyChanged:
public class Cell : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isVisible;
public bool IsVisible
{
get { return isVisible; }
set
{
if (isVisible != value)
{
isVisible = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(IsVisible)));
}
}
}
}
public class ViewModel
{
public List<Cell> Cells { get; } =
Enumerable.Range(0, 4096).Select(i => new Cell()).ToList();
}
The ItemsControl would use a UniformGrid to show the cell grid:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Cells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="64"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="10" Height="10" Fill="Green"
Visibility="{Binding IsVisible,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
To change the visibility of a cell:
((ViewModel)DataContext).Cells[index].IsVisible = true;
The simplest solution I see is to avoid any of [,] arrays at all:
public MyType[] RegularArray { get { return ReduceArrayDimensionality(TwoDimensionalArray); } }
You can proceed on nasty .Linq extensions to achieve that in a more handsome manner, I believe .SelectMany(array => array).ToArray() will do.
Then your binding becomes a regular one. The only thing you have to care about is appropriate BooleanToWahteverYouLikeConverter : IValueConverter - this guy is a part of shipped by the WPF binding system. You can find lots of tutorials and examples. Your converter has to be smart enough to figure out which exactly boolean to be considered this time. Use ConverterParameter to pass necessary index.
That's it.
KISS.

Setting `VirtualizingPanel.IsVirtualizing` with TestStack.White

When testing virtualized panels I need to set the VirtualizingPanel.IsVirtualizing Property so that Teststack.White can interact with them like with non virtualized panels.
This helps me especially when panels have a lot of content.
I do not want to set VirtualizingPanel.IsVirtualizing statically so I do not have to deliver it like that to my customers.
To play around with a minimal example you will need a window.
<Window x:Class="DataGridTest.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"
xmlns:local="clr-namespace:DataGridTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<DataGrid
AutomationProperties.AutomationId="MyDataGRID"
ItemsSource="{Binding MyItems}"
VirtualizingPanel.IsVirtualizing="True" >
<!-->
"IsVirtualizing Defaults to True."
"Setting this to False makes the test pass but is a poor choice for production code."
"Somehow I need to be able to change this programatically during testing."
</!-->
</DataGrid>
</Window>
Code behind for the window above.
using System.Collections.Generic;
using System.Windows;
namespace DataGridTest
{
public class Item
{
private string str;
public Item(string str) { this.str = str; }
public string Value { get { return str; } }
public int Length { get { return str.Length; } }
public int Hash { get { return str.GetHashCode(); } }
}
public partial class MainWindow : Window
{
List<Item> myitems;
public List<Item> MyItems { get { return myitems; } }
public MainWindow()
{
InitializeComponent();
myitems = new List<Item>();
for (int i = 0; i < 800; ++i)
{
myitems.Add(new Item($"Item {i}"));
}
DataContext = this;
}
}
}
And finally a Testing project:
using NUnit.Framework;
using System.Diagnostics;
using TestStack.White;
using TestStack.White.UIItems;
using TestStack.White.UIItems.WindowItems;
namespace NunitTest
{
[TestFixture]
public class Class1
{
private Application app;
private Window window;
[OneTimeSetUp]
public void OneTimeSetUp()
{
ProcessStartInfo info = new ProcessStartInfo( $"{TestContext.CurrentContext.WorkDirectory}/DataGridTest.exe");
info.WorkingDirectory = TestContext.CurrentContext.WorkDirectory;
app = Application.Launch(info);
window = app.GetWindow("MainWindow");
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
window.Close(); window = null;
app.Close(); app = null;
}
[Test]
public void test()
{
ListView list = window.Get<ListView>("MyDataGRID");
SetIsVirtualizing(list, false);
Assert.AreEqual(800, list.Rows.Count, "This fails for virtualized panels");
SetIsVirtualizing(list, true);
}
private void SetIsVirtualizing(ListView list, bool value)
{
//insert magic - I tried a couple of things but I just can not set this dependency property
}
}
}
Please help be to understand how VirtualizingPanel.IsVirtualizing can be set during testing.
I had some success with adding a collapsed textbox to interact with the datacontext. Although I am very unhappy about that solution it does pass the testing.
Here are modifications to the code that I made:
window
<StackPanel>
<TextBox
AutomationProperties.AutomationId="MyItems_IsVirtualizing_Injector"
Text="{Binding MyItems_IsVirtualizing_Injector}" Visibility="Collapsed"/>
<DataGrid
AutomationProperties.AutomationId="MyDataGRID"
ItemsSource="{Binding MyItems}"
VirtualizingPanel.IsVirtualizing ="{Binding MyItems_IsVirtualizing}"
>
<!-->
"IsVirtualizing Defaults to True."
"Setting this to False makes the test pass but is a poor choice for production code."
"Somehow I need to be able to change this programatically during testing."
</!-->
</DataGrid>
</StackPanel>
code behind
string injector = true.ToString();
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public string MyItems_IsVirtualizing_Injector
{
get { return injector; }
set
{
injector = value;
PropertyChanged(this, new PropertyChangedEventArgs("MyItems_IsVirtualizing_Injector"));
PropertyChanged(this, new PropertyChangedEventArgs("MyItems_IsVirtualizing"));
}
}
public bool MyItems_IsVirtualizing { get { return string.Equals(true.ToString(), MyItems_IsVirtualizing_Injector); } }
testing
private void SetIsVirtualizing(ListView list, bool value)
{
var injector = window.Get<TextBox>("MyItems_IsVirtualizing_Injector");
injector.Text = value.ToString();
}
EDIT: Since my actual usecase is counting the elements I actually settled for another solution that can be called using CountElements(list.AutomationElement)
private static int CountElements(AutomationElement container)
{
var patterns = container.GetSupportedPatterns();
var itemContainer = container.GetCurrentPattern(ItemContainerPattern.Pattern) as ItemContainerPattern;
List<object> elements = new List<object>();
var element = itemContainer.FindItemByProperty(null, null, null);
while (element != null)
{
elements.Add(element);
element = itemContainer.FindItemByProperty(element, null, null);
}
return elements.Count;
}

Button doesn't bind to function in viewmodel with caliburn micro

I have the following xaml code in my view:
<UserControl x:Class="Klanten_beheer.Views.CustomerListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit">
<DockPanel LastChildFill="True" Height="Auto" Width="Auto" Grid.Row="0">
<Button x:Name="ToLastRecord" Content="<<" Background="LightSkyBlue"/>
And the following code in my viewmodel:
public class CustomerListViewModel : PropertyChangedBase
{
#region Fields
private IObservableCollection<Client> m_clients;
#endregion
#region Properties
public int Position { get; private set; }
public IObservableCollection<Client> Clients
{
get { return Clients; }
set { Clients = value; }
}
public Client SelectedClient
{
get
{
if ((Position < m_clients.Count) && (Position > -1))
{
return m_clients[Position];
}
else
{
return m_clients[0];
}
}
set
{
Position = m_clients.IndexOf(m_clients.Where(x => x.ClientNumber == value.ClientNumber).FirstOrDefault());
}
}
#endregion
#region Constructors
CustomerListViewModel(int position)
{
using (ClientDbEntities ctx = new ClientDbEntities())
{
var m_clients = (from client in ctx.Clients
orderby client.ClientNumber
select client).ToList();
if (m_clients.Count != 0 && position < m_clients.Count)
{
Position = position;
}
else if (position >= m_clients.Count)
{
// Do nothing
}
else
{
Position = -1;
}
}
}
public CustomerListViewModel()
{
Position = 0;
}
#endregion
#region Public functions
public bool CanToLastRecord()
{
return true;
}
public void ToLastRecord()
{
System.Windows.MessageBox.Show("ToLastRecord");
}
#endregion
}
}
I would expect that the function ToLastRecord was trigger when the button is pushed.
For some reason the function is never triggert.
I know it's been awhile - but forever who'll come up to this,
you should really take a look at Caliburn.micro cheat-sheet
The answer is right there at the top of the page - in order to attach an action to a default event (on buttons clicks do count) you need to use
<Button x:Name="ToLastRecord" Content="<<" Background="LightSkyBlue" cal:Message.Attach="ToLastRecord"/>
Don't forget to add the following line to UserControl element:
xmlns:cal="http://www.caliburnproject.org"

Get the shape of a control as Geometry

Is there a way to get the shape of a control and convert it into a Geometry object?
I have the following situation: In a WPF application a popup will be shown. This popup is no windows, it's a control, that will get visible. The rest of the application will get darker as a gray layer is above the application.
Now the problem is that this gray layer is also above the popup itself which is caused by the design of the application plus the element that was clicked and opened the popup should also not be hidden by the layer. I decided to attach a clipping geometry to the gray layer which is fine, but I have to detect all forms and paths that I don't want to hide by myself.
So to get back to my question: Is there a way to get the shape of a control and convert it into a Geometry object? E.g. I found ways to get the VisualBrush of a control but also cannot convert that - or just do not see how it is possible.
you could do it this way:
Remove the button from the visual tree and place it on the adorner.
When the adorner closes attach it to the original parent again.
I think this is much more flexible than clipping any geometries and makes it much more flexible (you could e.g. place complex content like usercontrols on the adorner)
The following example uses a Panel as container for the button.
The Xaml (Window):
<Grid Margin="50" x:Name="myGrid" Background="LightBlue">
<Button x:Name="myButton" Width="80" Height="30" Click="myButton_Click">Show popup</Button>
Code Behind:
private FrameworkElementAdorner _adorner;
private Panel _originalParent;
private void myButton_Click(object sender, RoutedEventArgs e)
{
if (_adorner == null)
{
_adorner = new FrameworkElementAdorner(myGrid);
}
// remove the button from the parent panel and attach it to the adorner
// otherwise remove from adorner and attach to original parent again
if (_adorner.IsVisible)
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(myGrid);
adornerLayer.Remove(_adorner);
Panel parent = VisualTreeHelper.GetParent(myButton) as Panel;
if (parent != null)
{
parent.Children.Remove(myButton);
}
_originalParent.Children.Add(myButton);
}
else
{
_originalParent = VisualTreeHelper.GetParent(myButton) as Panel;
if (_originalParent != null)
{
_originalParent.Children.Remove(myButton);
}
// Create the Adorner with the original button in it
_adorner.Child = CreateAdornerContent(myButton);
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(myGrid);
adornerLayer.Add(_adorner);
}
}
/// <summary>
/// Creates some dummy content for the adorner
/// </summary>
private FrameworkElement CreateAdornerContent(Button myButton)
{
Grid g = new Grid();
g.Background = new SolidColorBrush(Colors.Yellow);
TextBlock tb = new TextBlock();
tb.Text = "I am the Adorner";
g.Children.Add(tb);
g.Children.Add(myButton);
return g;
}
And here the simple adorner which just displays a frameworkElement:
class FrameworkElementAdorner : Adorner
{
private FrameworkElement _child;
public FrameworkElementAdorner(UIElement adornedElement)
: base(adornedElement)
{
}
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
protected override Visual GetVisualChild(int index)
{
if (index != 0) throw new ArgumentOutOfRangeException();
return _child;
}
public FrameworkElement Child
{
get
{
return _child;
}
set
{
if (_child != null)
{
RemoveVisualChild(_child);
}
_child = value;
if (_child != null)
{
AddVisualChild(_child);
}
}
}
protected override Size ArrangeOverride(Size finalSize)
{
_child.Arrange(new Rect(new Point(0, 0), finalSize));
return new Size(_child.ActualWidth, _child.ActualHeight);
}
}
I can also upload the full sln if you like. Is this possible here somehow?

Categories