I'm learning to create WPF applications and I got a homework.
I have to create a wpf mvvm "tron lightcycle game", but unfortunately got stuck.
In the View (Mainwindow.xaml) there is a Canvas. I should draw here.
...
<Canvas Name="cnvs_game" Margin="5,5,5,5">
...
In the ViewModel there is a GameData class and a Timer.
Every tick the GameData updates(GameTime,Player1CanvasPosition (Point),... ).
I bid the Gametime to the View like this:
Mainwindow.xaml.cs:
...
<TextBlock Text="{Binding GameTime}" />
...
ViewModel.cs:
...
private GameData _GameData;
...
public String GameTime { get { return _GameData.GameTime.ToString(); } }
...
private void GameTimer_Tick(object sender, EventArgs e)
{
_GameData.Step();
OnPropertyChanged("GameTime"); // PropertyChanged with error handling
OnPropertyChanged("Player1CanvasPosition ");
OnPropertyChanged("Player2CanvasPosition ");
}
The GameTime refresh in the View. It wasn't hard. But I still have no idea how to draw.
How should I get the Player1CanvasPosition and draw there a Rectangle (in the Canvas). What is the best way to do this? Help Me Please! :S
You can do this the same way you did With the GameTime, for example:
<Canvas Name="cnvs_game" Margin="5,5,5,5">
<Rectangle Canvas.Left="{Binding Player1CanvasPositionX}" Canvas.Top="{Binding Player1CanvasPositionY}" ... />
<Rectangle Canvas.Left="{Binding Player2CanvasPositionX}" Canvas.Top="{Binding Player2CanvasPositionY}" ... />
...
And create the Player1CanvasPositionX property in the ViewModel which call the OnPropertyChanged, then when changing the properties in the ViewModel the rectangles will move.
Edit:
For dynamically adding rectangles I would use an ItemsControl which is bound to an ObservableCollection of positions. The ItemsControl datatemplate would contain a rectangle which would bind to the position. Look at this link for more details WPF Canvas, how to add children dynamically with MVVM code behind.
Related
I have a Canvas that contains a Rectangle. On that canvas, I bind a mousedown event to a command on the ViewModel. In that command, I am being passed the MouseEventArgs, but there the Target element is either the Canvas or the Rectangle. Where can I find in the MouseEventArgs the Canvas this event was fired from?
My code is more or less:
<Canvas Background="White">
<i:EventTrigger EventName="MouseLeftButtonDown">
<local:InteractiveCommand Command="{Binding CmdMouseLeftButtonDown}"/>
</i:EventTrigger>
<Rectangle Width="50" Height="50" />
</Canvas>
And in the ViewModel:
ICommand CmdMouseLeftButtonDown => new DelegateCommand<MouseEventArgs>(e =>
{
e.??? // <= Where do I find the Canvas here, whether I click on the Rectangle or Canvas?
}
Please do not answer with some hackish solution like e.MouseDevice.Target.Parent. This needs to work however complicated the element in the canvas is. It could contain another canvas for instance.
A view model is not supposed to have a reference to a UI element such as a Canvas or a Rectangle at all in the first place. This effectively breaks the MVVM pattern and that's why it makes no sense to pass the sender argument to the command.
You might as well get rid of the EventTrigger and invoke the command programmatically from the code-behind of the view:
<Canvas Background="White" MouseLeftButtonDown="Canvas_MouseLeftButtonDown">
<Rectangle Width="50" Height="50" Fill="Red" />
</Canvas>
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var yourViewModel vm = DataContext as YourClass;
vm.CmdMouseLeftButtonDown.Execute(sender as Canvas); //<-- pass the Canvas as a command argument or create a new command argument type that holds a reference to the Canvas
}
This is certainly not any worse than your current approach as far as the MVVM pattern is concerned. You are still invoking the very same command from the very same view and MVVM is not about eliminating code. It is about separation of concerns.
Your MouseEventArgs.Source will reference to the Canvas in any case but the MouseEventArgs.OriginalSource will referece to the Rectange if you have clicked on its area. It will be the control determined by pure hit testing.
Set <Canvas Background="Transparent" ... />
as answered in the following question by #Rob Fonseca-Ensor:
WPF: Canvas mouse events not firing on empty space
I am using an Oxyplot control in my WPF application. Is there a MVVM friendly way to re-wire the right click pan action to happen with a left click?
My current wpf code is
<oxy:PlotView Model="{Binding MyData}" Grid.Column="1" Grid.Row="0" />
I'd suggest you to create a User Control:
myUC.xaml
<oxy:PlotView x:Name="PlotView1" Model="{Binding **MyPlotModel**}" />
myUC.xaml.cs
public partial class myUC : UserControl
{
public myUC()
{
InitializeComponent();
PlotView1.Controller = new OxyPlot.PlotController();
/* Events Managment */
PlotView1.Controller.UnbindAll();
PlotView1.Controller.BindMouseDown(OxyMouseButton.Left, PlotCommands.PanAt);
}
}
Note I have replaced Mydata by MyPlotModel, if you want to directly bind data use Plot instead.
It is not really MVVM friendly because you'll have to use this UC, but you can change its DataContext to bind it to your ViewModel.
Normally you could bind a Controller created from your viewModel just like this:
<oxy:PlotView x:Name="PlotView1" Model="{Binding **MyPlotModel**}" Controller="{Binding MyPlotController}"/>
But it seems bugged and I could not figure out why. May be this will help you
https://github.com/oxyplot/oxyplot/issues/436
Let me know if you can solve it without UC.
I have an "array" of text which dynamically grows as data comes into the application, and I would like to be able to scroll (programmatically, not via direct user input) that array vertically, as well as add to it. I tried to put the data into a DataGrid but that's not really what I want (unless I heavily modify the DG which I'm hoping to avoid). I don't need the array contents to be selectable, just viewable and I will scroll the "current" array item into view. What WPF element(s) should I be using to display and dynamically grow the list?
Edit:
So here is my current XAML snippet:
<Canvas Grid.Column="1" Background="#FFF8D2D2" ClipToBounds="True">
<ItemsControl Canvas.Top="20" Canvas.Left="20" Name="PipeQueueIC" Height="45" Width="272" BorderThickness="1" BorderBrush="#FF149060" />
</Canvas>
and here is my code behind:
DoubleAnimation scrollQueue = new DoubleAnimation();
scrollQueue.By = -16;
scrollQueue.Duration = TimeSpan.FromSeconds(0.5);
PipeQueueIC.BeginAnimation(Canvas.TopProperty, scrollQueue);
However the whole ItemsControl is moved up and it does not "scroll" through the "viewing window". What am I doing wrong here?
Use an ItemsControl:
<ItemsControl ItemsSource="{Binding SomeListOfString}"/>
ViewModel:
public class ViewModel
{
public ObservableCollection<string> SomeListOfString {get;set;}
public ViewModel()
{
//... Initialize and populate the Collection.
}
}
In a Silverlight game I'm working on I'm using an ItemsControl to display an ObservableCollection of game objects that we'll call Foo. Foo implements INotifyPropertyChanged and has a single property: Radius. The ItemsControl's ItemTemplate represents each Foo as an circular path, with the radius of the path bound to Foo.Radius.
The problem I'm running into is that whenever I try to add something to the ObservableCollection I get an InvalidOperationException with the message "Operation is not valid due to the current state of the object." If a remove the RadiusX and RadiusY bindings program runs fine, and it still works if I bind Foo.Radius to some property of Path. I'm at a loss for how to bind the geometry properties. Am I missing something?
XAML for reference:
<ItemsControl ItemsSource="{Binding}" x:Name="LayoutRoot">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Stroke="Black">
<Path.Data>
<EllipseGeometry RadiusX="{Binding Radius}" RadiusY="{Binding Radius}" />
</Path.Data>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Codebehind:
private ObservableCollection<Foo> things = new ObservableCollection<Foo>();
public MainPage()
{
InitializeComponent();
LayoutRoot.DataContext = things;
CompositionTarget.Rendering += Update;
}
void Update(object sender, EventArgs e)
{
things.Add(new Foo());
}
Try this ...
Dispatcher.BeginInvoke(() => things.Add(new Foo()));
I did a bit more searching and discovered that in Silverlight 3 it's only possible to bind properties to FrameworkElements, but Geometry inherits from DependencyObject. Upgrading the project to Silverlight 4 seemed to fix the problem.
I want to make a WPF ListBox photo album for one my college projects.
I need to design a DataTemplate/ListBox style so it will look like a stack jumbled of photos, i.e., the top one being the item in focus/selected (see diagram below).
Image here
With reference to the drawing,
item 1) is not shown
item 2) is at the back of stack
item 3) in the middle of 2 and 4
item 4) is in focus
item 5) is not shown
I am having the most trouble getting the items to rotate and overlap and the most difficult task is getting the item in focus to be shown on top.
I'm using Visual Basic because I haven't yet mastered C# so it would be useful if examples could be in VB or use mainly WPF.
To get the items to rotate, you should look at using Transforms. The one that is most relevant in this case is the Rotate Transform, also to give it that random scattered apperance, we can use an ObjectDataProvider and generate our angles somewhere else.
I don't know VB, but the only C# involved in this is pretty simple, and should be easily transferable.
Lets just use something simple like Colors, which can easily be switched over to image resource paths. Here we've got an ObservableCollection of our Colors, and also a separate class that we will use to generate angles, all it's going to do is return a number between 0 and 360, which we will use to rotate each item.
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Colors = new ObservableCollection<string>();
Colors.Add("Red");
Colors.Add("Blue");
Colors.Add("Green");
Colors.Add("Yellow");
this.DataContext = this;
}
public ObservableCollection<string> Colors
{
get;
set;
}
}
public class AngleService
{
private static Random rand = new Random();
public int GetAngle()
{
return rand.Next(0, 90);
}
}
In the XAML, we can now create a resource that can be used to generate the angles.
<Window.Resources>
<local:AngleService x:Key="Local_AngleService" />
<ObjectDataProvider x:Key="Local_AngleProvider"
x:Shared="False"
ObjectInstance="{StaticResource Local_AngleService}"
MethodName="GetAngle" />
</Window.Resources>
Now, we just need to create something to display our items. We can put them in a ListBox and add a data template to set the background for each color item, as well as apply the RotateTransform.
<ListBox ItemsSource="{Binding Colors}"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center">
<ListBox.ItemTemplate>
<DataTemplate>
<Border x:Name="uiItemBorder"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="3"
Background="{Binding}"
Width="50"
Height="50">
<TextBlock Text="{Binding}"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
<Border.RenderTransform>
<RotateTransform Angle="{Binding Source={StaticResource Local_AngleProvider}}" />
</Border.RenderTransform>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The UI still needs a bit of work from there, but that should help out with the rotation of the items.