WPF CAD-like system - all data in classes - wpf

Still picking my way through learning XAML/WPF. If someone can show me how to accomplish the following - it will go a long way to helping me develop my next project (and several similar projects down the road).
Say I have a collection of objects that defines objects to be drawn on a canvas. The objects contain all the information necessary to render the objects, including the shape, color, and location. Can a XAML control be created that binds to this collection and handles the rendering, or is this better done by drawing on the canvas in the code-behind?
One other point - the objects must eventually be click-selectable, selectable via rectangle-lasso, and draggable. This doesn't have to be solved in the example code someone supplies, but I thought it might be relevant to know this as it might affect the various implementations.
Example class below. thanks in advance.
Class DrawingElement
readonly property Shape as string ("circle", "square", "triangle")
readonly property Position as point (coordinates)
readonly property Color as string ("red", "blue", "yellow")
end class
Sub Main
dim lst as new List(of DrawingElement)
lst.add(new DrawingElement("Circle", 10,20, "Blue"))
lst.add(new DrawingElement("Square", 80,35, "Red"))
lst.add(new DrawingElement("Triangle", 210,120, "Yellow"))
<draw lst!>
End Sub

Can be done, but not by using magic strings (e.g., "circle") like in your example.
First, you should be designing your models based on existing framework elements rather than designing the model with the idea of whipping up some new UI elements or struggling to create code that interprets between them.
WPF already has an ellipse (circle), rectangle (square) and a whole host of other geometric primitives for you to use. You'll want to create models that contain public bindable properties that you can bind to instances of these elements to control their shape and location.
Without going into much detail (or testing), I'd do something like this
public class GeometricElement : INotifyPropertyChanged
{
// these are simplified and don't show INPC code
public double Left {get;set;}
public double Top {get;set;}
public Brush Fill {get;set;}
// ...
}
// ...
public class Square : GeometricElement
{
public double Width {get;set;}
public double Height {get;set;}
}
// ...
// bound to the window
public class CadAppDataContext
{
public ObservableCollection<GeometricElement> Elements {get; private set;}
}
And in my xaml, it would look something like
<ItemsControl ItemsSource="{Binding Source={StaticResource cadAppDataContext}}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<DataTemplate TargetType="{x:Type me:Square}">
<Rectangle
Canvas.Left="{Binding Left}"
Canvas.Top="{Binding Top}"
Width="{Binding Width}"
Height="{Binding Height}"
Fill="{Binding Fill}" />
</DataTemplate>
<!-- more DataTemplates for circle, triangle, etc etc -->
</ItemsControl.Resources>
</ItemsControl>
The ItemsControl will create a Canvas element and for every GeometricElement in my Elements collection it will add a new child UI element based on the type of object in Elements.
Items controls are bound to collections and can add or remove elements based on what's happening in the collection within your code. It determines the UI element to add by looking for a DataTemplate that is designed for a particular Type. This is a common, and important, pattern and is why using magic strings will hurt you in the long run; the magic string route won't let you leverage the power of the framework already built into WPF.
Now, I'm not saying this will work out of the box. You'll probably run into properties that won't bind without some heavy lifting. You might even have to extend the geometry primitives to get them to behave how you want. But this is the pattern used in WPF applications. Understanding the pattern and using it will help you avoid hours of stress and failure.

Related

How to build form at runtime in WPF MVVM based application

How to build form at runtime in WPF-MVVM (PRISM) based application.
Requirement is like user should be able to add control like textbox, checkbox, combobox etc. at runtime.
after adding the crontol user will save the form and all the configuration will get saved in database.
So that application can create the form at runtime based on the configuration stored in database.
How can we achieve this?
Thanks.
I've done something similar, although not with standard UI controls. I have a series of classes representing the "controls" I want to display - in my scenario these represent physical devices like pumps, valves, switches, displayed on a machinery "control panel" that the user can configure. These classes inherit from a base class (call it "HardwareItem") which exposes some properties that are common to all controls, e.g. Top, Left, Width, Height, Tooltip, etc.
The "designer"
The window where the user "designs" a form consists of the following components:-
A "toolbox", basically an ItemsControl bound to a VM List<HardwareItem> property that exposes the available HardwareItems (created and populated by the VM's constructor)
A canvas, that the user can drag items onto from the toolbox. When a drop happens, I instantiate the appropriate HardwareItem object and add it to a collection (used to keep track of what controls have been added). To render the control on the canvas, I create a ContentControl and set its "Source" property to the HardwareItem object, then add that to the canvas at the drop position. The control's visual is rendered using XAML DataTemplates that I've created for each HardwareItem type.
A PropertyGrid control (part of the free Xceed toolkit). When the user selects a control on the canvas, the corresponding HardwareItem object is wired up to the PropertyGrid, allowing the user set its property values (I use a custom attribute to control which properties should appear in the grid).
When the user clicks "save", I basically just serialize my collection of HardwareItem objects to a string using Json.Net then saved to file.
"Runtime"
To render a previously designed form, the file is deserialized back into a collection of HardwareItem objects, and added to a canvas in pretty much the same way as described above.
Doing something similar with standard WPF controls shouldn't be too dissimilar. You could create classes that expose just the properties that you want a user to manipulate, e.g.:-
// Base class
public class MyControl
{
public double Top {get;set;}
public double Left {get;set;}
public double Width {get;set;}
public double Height {get;set;}
}
// TextBox
public class MyTextBox : MyControl
{
public string Text {get;set;}
}
// Button
public class MyButton : MyControl
{
public string Caption {get;set;}
public ICommand ClickCommand {get;set;}
}
The DataTemplates might look something like this:-
<DataTemplate DataType="{x:Type MyTextBox}">
<TextBox Text="{Binding Text}"
Width={Binding Width}" />
</DataTemplate>
<DataTemplate DataType="{x:Type MyButton}">
<TextBox Content="{Binding Caption}"
Width={Binding Width}"
Height={Binding Height}"
Command={Binding ClickCommand} />
</DataTemplate>
Much of the canvas manipulation is done in code-behind rather than the VM. It's "UI logic" so is a perfectly acceptable approach.
Prism is not used at all here (I use it for navigating between views, but it doesn't play a part in this "form designer" functionality).

MahApps panoram control not displaying photo's

I want use the MahApps PanoramaControl to display a bunch of photo's. However I see only the string paths appear in the control, which will be correct if you look at my code.
But I can't figure out howto get it working to show the images instead of the links.
Xaml:
<Controls:Panorama Grid.Row="2"
Grid.ColumnSpan="4"
ItemBox="140"
ItemsSource="{Binding PhotoCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
ViewModel:
string[] filePaths = Directory.GetFiles(#"D:\Google Drive\Images\Backgrounds");
test = new PanoramaGroup("My Photo's", filePaths);
PhotoCollection = new ObservableCollection<PanoramaGroup> { test };
Anyone an idea on how to make it show the images? The control is load as I can scroll sideways on the text.
There is not much documentation on their site on how to get it working...
Or are you using some other Metro style lib for the 4.0 framework?
In order to get MahApps Panorama control i would offer the follow solution below. As to other frameworks that provide this level of detailed MODERN UI experience I have not come across any but interested to know if you do.
Hope the solutions works for you.
You need to add a Data Template the represent the object you're showing.
Start off by defining the object in a POCO class (as illustrated below). Then in the population of the items ensure you translate them to the newly created POCO object instead of leaving them as string values.
public class Photo {
public string Path { get; set; }
}
Next in the definition of you XAML window you need to create a reference to the namespace where the POCO object resides.
xmlns:model="clr-namespace:project.models;assembly=project"
Last, but not least, you want to then create a DataTemplate to represent the object. This is added to the window resources.
<Window.Resources>
<DataTemplate DataType="{x:Type model:Photo}">
<Image Source="{Binding Path}"/>
</DataTemplate>
</Window.Resources>
This should then let the render take place in the interface where it belongs. Hope this works for you.

Silverlight databinding - How do I dynamically change the style of elements based on previous elements?

I've got a ListBox that contains an alphabetized list of words. For each letter, I'd like the first word to be blue and all other words to be white. I'd done this previously by looping through the words, creating TextBlock controls with the appropriate Foreground color, and adding them manually to the ListBox control. I'd like to do this with databinding, though. Is there an elegant way to apply this sort of conditional formatting with databinding?
This is the sort of thing that you typically use a ViewModel for. What you could do is to create a WordViewModel class that looks something like this (but presumably with INotifyPropertyChanged implementations, etc.):
public class WordViewModel
{
public string Word {get; set;}
public Color ForegroundColor {get; set;}
}
When you add the WordViewModel instances to your ObservableCollection<WordViewModel>, you would then set the appropriate properties based on roughly the same logic you were using before.
Your ListBox would then look something like this:
<ListBox ItemsSource="{Binding MyWords}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Word}" ForegroundColor="{Binding ForegroundColor}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Having a "ForegroundColor" in a Model would of course be a complete violation of the "separation of concerns". It would be most appropriate in a View if there were a clean and easy way to do it. But given the circumstances, unless someone can think of a better way to handle it in XAML, I think it's reasonably appropriate to place it in the ViewModel.

In WPF how can I restrict the type of children in a Panel?

We want to create a subclass of Canvas that only allows children of a specific type (they need to have intimate knowledge of our subclass and vice-versa.) That said, is there any way to force a panel to only accept children of a certain type (or types)?
M
The solution we came up with was to simply subclass the Canvas, then monitor the children. If one is added that's not of the type we want, we instantly remove it and throw an error. Won't stop compile-time errors but does the trick.
Extending this further I was thinking about also subclassing the canvas, then Newing over the Children property to return our own collection which we've internally synced to the panel's children via binding. That way we can also have compile-time support. Granted if someone casts our subclass to a straight canvas, then obviously the 'new'd Children property won't be accessed (its a 'new' not an override) but the aforementioned collection monitoring will still give us what we want.
It would have been nice if the WPF team had come up with a generic canvas so we could do something like canvas but that obviously wouldn't work in XAML unless they somehow came up with syntax for that. Then again, a canvas is pretty damn basic so maybe we'll just roll our own geeneric version where we could do something like this...
public class TypedCanvas<t> : PanelBase
{
// Implementation here
}
public class FooCanvas : TypedCanvas<Foo>{}
public class LaaCanvas : TypedCanvas<Laa>{}
...of which we could then use FooCanvas and LaaCanvas via XAML while still getting all the benefits of using generics.
Even better, make it TypedPanelBase so we could use it with any other custom panel as the base type.
Actually, now that I've typed this... I think I'm about to go re-write our canvas to try this approach! (Either way, I now have a solution which is what we were after.)
Actually... no way.
Besides, I don't understand your goals. If you need to work with some specific containers just cast Panel.InternalChildren:
this.InternalChildren.OfType<MyType>().Do(...);
Consider about scenario: you have a collection of strings, which is the source for ItemsControl. In DataTemplate we have button which content is binded to item from mentioned collection. And ItemsControl.ItemsPanel is Canvas.
public IEnumerable<string> Items
{
get;
}
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
So, what items types do you want to restrict? Buttons or strings?
The problem in this scenario is that ContentPresenters will be effective visual children of Canvas. But in overriden method OnVisualChildrenChanged (where you could try to check item type) Content and ContentTemplate properties are set to null due to deferred binding.
So the one acceptable solution I can propose is creating your own ItemsControl, which returns some concrete container instead of ContentPresenter:
public class MyItemsControl : ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new Button();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is Button;
}
}
<self:MyItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<self:MyPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</self:MyItemsControl>
With this approach, you guarantee that your item containers (Panel.InternalChilder) are buttons (or something) and in MyPanel you could safely cast:
this.InternalChildren.Cast<Button>()

how to build dynamic grid and binding to xaml using mvvm

I'm planning a WPF application which will build dynamic grid with textblocks in the viewmodel and then refresh interface (xaml) with the new grid.
I've done the firts step, but i have problems to refresh the view with the new grid.
Is there any example code of how to bind the grid to the xaml that I can have a look at?? I really can't figure this out!
Thanks
You may be approaching this slightly wrongly, hard to say from the question-
Generally to show a dynamic set of UI elements in MVVM you bind the ItemsSource property of an ItemsControl to an ObservableCollection. The ItemsControl ItemsTemplate property converts the YourViewModel object into a UIElement which can be a TextBlock or whatever style you want.
So as an example:
// model
class Person
{
public string Name {get; private set;}
}
// view model
class MainViewModel
{
public ObservableCollection<Person> People {get; private set;}
}
//view
<UserControl DataContext="{Binding MyMainViewModelObject}">
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemsTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>/
</ItemsControl.ItemsTemplate>
</ItemsControl>
</UserControl>
I havent tested that code, it is just to illustrate. There are other ways of dissecting the problem into MVVM, it all depends on the situation. You would have to give more details for us to help you out with that. Rarely in WPF is there a need to use code to create or add UI elements to other UIElements etc.
A point to note more along the exact lines of the question however is that an ItemsControl can either bind to a bunch of regular objects and use it's template to create UIElements from them, OR it can bind to a list of UIElements, in which case the template is not applied (sounds like this is the situation you have).

Resources