Is there a way in WPF to access the ScaleTransform Parameter of a Canvas Panel (which is created in the xaml File) in the Code Behind File?
Use Case Example:
I want to position a list of items inside a scaled Canvas like so:
<ItemsControl MouseMove="Control_MouseMove">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform>
<ScaleTransform.ScaleX />
</ScaleTransform>
</TransformGroup>
</Canvas.RenderTransform>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I also want to display the current position of my items inside the Canvas at Mouse over with a Pop-up. I am currently trying it this way:
public void Control_MouseMove(object sender, MouseEventArgs e) {
if (!this.Popup.IsOpen)
this.Popup.IsOpen = true;
var mousePosition = e.GetPosition(this.SwLane);
this.Popup.HorizontalOffset = mousePosition.X + 10;
this.Popup.VerticalOffset = mousePosition.Y - 20;
this.PopupContent.Text = System.Convert.ToString(mousePosition.X);
}
What I get is the canvas X coordinate inside the Popup (which makes sense to me). However, I would like to display the scale transformed "coordinates" of the canvas.
Is there a way to access the ScaleTransform Parameter in Code Behind so that I can visualise my transformed item position? Or should I do it in a different way? Thanks.
You could set an OnInitialized Handler where you build your transform in code-behind, save its constituent ScaleTransform as a private property and then assign it there as well after your window has initialized
Something crudely like this:
<Window x:Class="WpfApp4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="White"
MouseLeftButtonDown="MainWindow_OnMouseLeftButtonDown"
Initialized="MainWindow_OnInitialized"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ItemsControl x:Name="MyItems" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Rectangle Width="100" Height="200" Stroke="Red" StrokeThickness="5"/>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private TransformGroup MyTransform { get; set; }
private ScaleTransform SceneScale { get; set; }
private void MainWindow_OnInitialized(object? sender, EventArgs e)
{
MyTransform = new TransformGroup();
SceneScale = new ScaleTransform(1.0, 1.0, 0, 0);
MyTransform.Children.Add(SceneScale);
MyItems.RenderTransform = MyTransform;
}
}
Now you could add a MouseLeftButtonDown Handler to scale things, for example:
private void MainWindow_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
SceneScale.ScaleX *= 1.1;
SceneScale.ScaleY *= 1.1;
}
For the coordinate thing, expose the current coordinates as properties. Update them when the mouse moves. Bind the UI to them
This might work better if you packaged it all in a control
Related
I want to implement a horizontal ListBox, with multiple lines.
Using WrapPanel:
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I get the following result:
But I am looking for this result:
How can I implement this?
Based on your comments, I made a UniformGrid which adjusts the number of rows/columns, whenever the window is resized.
MainWindow.xaml:
<Grid x:Name="Container">
<ListBox HorizontalContentAlignment="Stretch">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid x:Name="ItemsGrid" Loaded="ItemsGrid_Loaded" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
The names and events are important. They are used for adjusting the columns/rows in the code file.
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private UniformGrid itemsGrid;
public MainWindow()
{
InitializeComponent();
}
private void ItemsGrid_Loaded(object sender, RoutedEventArgs e)
{
// Set reference and adjust columns/rows once the UniformGrid is loaded.
itemsGrid = sender as UniformGrid;
((UniformGrid)sender).Columns = (int)(Container.ActualWidth / 250);
((UniformGrid)sender).Rows = (int)(Container.ActualHeight / 75);
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
// Adjust number of columns/rows whenevner the window is resized.
if (itemsGrid != null)
{
itemsGrid.Columns = (int)(Container.ActualWidth / 250);
itemsGrid.Rows = (int)(Container.ActualHeight / 75);
}
}
}
This could definitely be cleaned up a bit, but it seems to do the job. There might be more elegant solutions, but I couldn't find any...
I'm attempting to simulate a kind of simple CAD-like behaviour using WPF, where a polygon is drawn onto a canvas, and the points of the polygon can be moved around.
I've managed to get it so that a PointCollection property is displayed on the canvas by setting up an ItemsControl with an Ellipse for each Point in the collection. The polygon displays correctly, so I know the Binding is working Source-To-Target. I've set up some simple events to be able to 'pick up' points of the polygon, and move them around. The events work fine, and the polygon does change on the canvas. However, any changes I make to the point positions are not getting pushed to the Source Property.
Any clues on how to get the Binding to work in the opposite direction? Is this sort of thing (Two-Way binding inside ItemsControl) even possible? Or perhaps there's another approach to this!
Thanks
XAML
<Canvas Width="500" Height="300" MouseMove="MoveMouse">
<ItemsControl ItemsSource="{Binding Path=MyPath, Mode=TwoWay, NotifyOnSourceUpdated=True }">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Ellipse Width="20"
Height="20"
Canvas.Left="{Binding Path=X, Mode=TwoWay, NotifyOnSourceUpdated=True }"
Canvas.Top="{Binding Path=Y, Mode=TwoWay, NotifyOnSourceUpdated=True }"
Fill="Red"
Margin="-10,-10,0,0"
MouseDown="ClickPoint" />
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
MainWindow.cs
public partial class MainWindow : Window
{
public PointCollection MyPath { get; set; } = new PointCollection{ new Point(10, 10), new Point(100, 100), new Point(200, 100) };
public Ellipse selectedPoint;
public MainWindow()
{
InitializeComponent();
}
private void MoveMouse(object sender, MouseEventArgs e)
{
if (selectedPoint == null) return;
//Move selected point to mouse position
selectedPoint.SetValue(Canvas.LeftProperty, e.GetPosition(sender as UIElement).X);
selectedPoint.SetValue(Canvas.TopProperty, e.GetPosition(sender as UIElement).Y);
//Print out MyPath points to check if source has changed
Console.WriteLine("");
foreach (Point p in MyPath)
Console.Write(p + " - ");
}
private void ClickPoint(object sender, MouseButtonEventArgs e)
{
//Pick up point, if no point is currently selected
selectedPoint = selectedPoint == null ? (Ellipse)sender : null;
}
}
I have a longListSelector that create several canvas dynamically and I want to draw in each canvas by using data from my ObservableCollection Games.
Here is my base code of the main page:
<Grid x:Name="ContentPanel">
<phone:LongListSelector Name="myLLS" ItemSource="{Binding GamesVM}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel>
<Canvas /> <!-- Here I want to draw -->
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
public class GameVM : INotifyPropertyChanged {
private string _title;
public string Title {
get { return this._title; }
set {
if (this._title!= value) {
this._title= value;
this.OnPropertyChanged("Title");
}
}
}
public void Draw() {
Ellispe stone = new Ellipse();
// [...] Add Fill, Strock, Width, Height properties and set Canvas.Left and Canvas.Top...
myCanvas.Children.Add(stone);
}
}
I would like to execute my Draw method when my GamesVM collection is generated but I haven't access to the corresponding canvas at this time. Putting my Draw method in code behind doesn't help because I have no event to handle where I could get both data binding object and the canvas newly generated (except if I miss something...). So I have no "myCanvas" instance in my Draw method.
I have some ideas to do that but nothing work well.
Option 1
I can put my UIElement (Ellipse, Line, etc) in an ObservableCollection which is binded in an ItemsControl like this :
<ItemsControl ItemsSource="{Binding myUIElements}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
public void Draw() {
myUIElements = new ObservableCollection<UIElement>();
Ellispe stone = new Ellipse();
// [...] Add Fill, Strock, Width, Height properties and set Canvas.Left and Canvas.Top...
myUIElements.Add(stone);
}
It works but when I leave the page and come back, I get an Element is already the child of another element exception.
If I use VisualTreeHelper to find my ItemsControl and call Items.Clear() on it, I get an exception too beacuse Items is read-only.
Option 2
I can use a ContentControl instead of ItemsControl and create the canvas in my Draw method:
<ContentControl Content="{Binding myUICanvas"/>
public void Draw() {
myUICanvas = new Canvas();
Ellispe stone = new Ellipse();
// [...] Add Fill, Strock, Width, Height properties and set Canvas.Left and Canvas.Top...
myUICanvas.Children.Add(stone);
}
It works too but when I leave the page and come back, I get a Value does not fall within the expected range exception.
I understand that I can't bind UIElement because I can't clear them when the Framework try to set them again. What is the trick to say "Please, do not add the same element twice" ?
Option 3
I can try to draw directly in XAML and bind a ViewModel object instead of UIElement object.
<ItemsControl ItemsSource="{Binding myDatas}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="{Binding Diameter}" Fill="Black" ...>
</Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It could work in WPF but in my Windows Phone 8 app, I have no ItemContainerStyle property to set Canvas.Left and Canvas.Right. Beside I would have to use a CompositeCollection to deal with several kind of shapes but DataType is not recognized by Visual Studio.
Moreover, even if it works with Line UIElements, the render is slower than c# approach.
So, what is the best option and how to deal with my exceptions ?
For information, I give you which one I choose.
I take option 2 and avoid the come back error by redrawing a new Canvas each time. I change my Draw definition so it return me the new Canvas.
public class GameVM : INotifyPropertyChanged {
// Title and other properties
private Canvas _myUICanvas;
public Canvas myUICanvas
{
get {
_myUICanvas = Draw();
return _myUICanvas;
}
set {
// this is never called
_myUICanvas = value;
}
}
public Canvas Draw() {
Canvas newCanvas = new Canvas();
Ellispe stone = new Ellipse();
// [...] Add Fill, Strock, Width, Height properties and set Canvas.Left and Canvas.Top...
newCanvas.Children.Add(stone);
return newCanvas;
}
}
Like this, I can run my program without error and without reloading/recreating all the GameVM instances.
I have used the scrollviewer in my app to show images. i need to move the stackpanel inside the scrollviewer right side when the user touches the right side of the display,and to move on left when user touches the left side. I tries in XNA framework but vector2 class does not check the postion of finger. How to achieve this.
You should be able to hook into the MouseLeftButtonDown event of the page. And then calculate where the user touched the screen with the MouseEventArgs.GetPosition Method. Here's a basic example to demonstrate this concept:
XAML
<ScrollViewer Background="Red">
<StackPanel x:Name="MyStackPanel"
Orientation="Vertical"
Width="150"
Background="Black"
HorizontalAlignment="Left">
<ItemsControl ItemsSource="{Binding Images}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Width="48" Height="48" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
Code Behind
public MainPage()
{
InitializeComponent();
this.MouseLeftButtonDown += OnMouseDown;
}
private void OnMouseDown( object sender, MouseButtonEventArgs e )
{
Point pos = e.GetPosition( this );
double half = this.ActualWidth / 2;
if( pos.X < half )
{
MyStackPanel.HorizontalAlignment = HorizontalAlignment.Right;
}
else
{
MyStackPanel.HorizontalAlignment = HorizontalAlignment.Left;
}
}
Using an image inside of a viewbox (inside a DockPanel), I've created a image that scales with my window in a wpf application.
<Viewbox HorizontalAlignment="Left" Name="viewbox1" VerticalAlignment="Top" Stretch="Uniform" StretchDirection="UpOnly">
<Image Height="438" Name="image1" Stretch="Uniform" Width="277" Source="/MyAPP;component/Images/TicTacToeBoardForExample.png" MouseDown="image1_MouseDown" />
</Viewbox>
How can I make certain regions of the picture run different code when clicked?
I'm desiring to do this in such a fashion that no matter what size the image scales (upon window resizing), the exact regions desired, scale perfectly with the image so that, when clicked, it always triggers their corresponding code-to-be-run for that region.
Already an old question, but as I had a similar requirement, I implemented the suggestion from Terry and want to share it with you. I use 10x10 Buttons/regions.
The View:
<Grid>
<Image Stretch="Fill" Panel.ZIndex="1" Source="{Binding BitMapSource}" />
<ItemsControl Panel.ZIndex="2" ItemsSource="{Binding RectangleItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="10" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Opacity="0.3" Content="{Binding Tag}"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
The ViewModel:
public class MainWindowViewModel : PropertyChangedBase
{
public MainWindowViewModel()
{
InitRectangles();
}
public BitmapSource BitMapSource => new BitmapImage(new Uri(#"C:\tmp\images\plus.png"));
public ObservableCollection<RectangleItem> RectangleItems { get; set; }
private void InitRectangles()
{
RectangleItems = new ObservableCollection<RectangleItem>();
for (var i = 0; i < 10; i++)
{
for (var j = 0; j < 10; j++)
{
RectangleItems.Add(new RectangleItem
{
X = j * 10,
Y = i*10,
});
}
}
}
}
And the RectangleItem (Model)
public class RectangleItem
{
public string Tag => $"{X}, {Y}";
public double X { get; set; }
public double Y { get; set; }
}
Off memory you could do something like this:
public void image1_MouseDown(object sender, MouseEventArgs e)
{
var pos = e.GetPosition(viewbox1);
if (/* pos in range 1 */) DoTheThingInRange1();
else if (/*pos in range 2*/) DoTheThingInRange2();
else if (/*pos in range 3...*/) DoTheThingInRange3();
//so on...
}
HTH
Your exact requirement is not clear, But it would be helpful if you take a look at the TranslatePoint and PointToScreen methods on the FrameworkElement.
i once needed to do a similar thing.
What is did was put buttons over the image. Then u put their opacity to 0 or 3.0 or something, so that u don't see the buttons, but can still click them.
When properly added, the buttons can resize along with the image.