Having a usercontrol that change to be edited? - c#

I've previously written several application in WPF, but I'm not sure how I should do this one:
I'm trying to make an "Universal App", designed toward the small screen on a raspberry Pi with win10 Iot.
I would like to create a usercontrol, which displays a value, and which when clicked expands to take up the full screen, allowing me to edit it nicely with some additional buttons (which show in the expanded version of the usercontrol)(e.g, numerical stepped Up/Down, + Ok/Cancel buttons). And when I click on the Ok Button in this expanded usercontrol, it should copy the EditedValue to the realValue (vars).
I'm a little stuck on how to do the part with a different display (different layouts, different components, taking all the place of the windows) and would like some help.

#Jordy van Eijk provided you with a viable solution but since you asked for an example I will provide you with my own implementation. Please note there are plenty of variations and other ways you may do this, and as your question seems to be quite broad I will only fill in the initial design.
My approach uses the MVVM design pattern:
A content presenter will bind to a view model and show the current data template. The data template will relate a view model to the user control. The user control contains your view, bindings to resize your selected item, and triggers to show/hide your extended display.
The Content Presenter (MainWindow.xaml):
<ContentPresenter Content="{Binding CurrentView}"/>
The Data Template (MainWindow.xaml):
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:UserControl1ViewModel}" >
<view:UserControl1/>
</DataTemplate>
</Window.Resources>
The User Control (UserControl1.xaml):
<UserControl.Resources>
<Style x:Key="ExtendedControl" TargetType="Button">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid HorizontalAlignment="Left" Background="Blue" Width="525">
<Button Content="Resize Control" VerticalAlignment="Top" HorizontalAlignment="Left" Width="{Binding Width}" Height="265" Command="{Binding ResizeCommand}" Margin="10,30,0,0"/>
<Button Content="Extended Control" Style="{StaticResource ExtendedControl}" Margin="383,32,25,258"/>
</Grid>
The User Control 1 View Model (UserControl1ViewModel.cs):
public class UserControl1ViewModel : ViewModelBase
{
public ICommand ResizeCommand
{
get
{
if (_resizeCommand == null) _resizeCommand = new RelayCommand(ResizeButton, () => true);
return _resizeCommand;
}
}
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
RaisePropertyChanged("IsVisible");
}
}
public double Width
{
get { return _width; }
set
{
_width = value;
RaisePropertyChanged("Width");
}
}
private RelayCommand _resizeCommand;
private bool _isVisible;
private double _width;
public UserControl1ViewModel()
{
Width = 100.0;
IsVisible = false;
}
private void ResizeButton()
{
Width = Application.Current.MainWindow.ActualWidth * .65;
IsVisible = true;
}
}
Before Click:
After Click:
This outlines the main pieces you will need to create a base application like you are specifying. When the resize control is pressed, its width binding is changed to expand its size to 65% of the application main window and the visibility binding of the extended control button is changed to true. Id include pictures of the resize but my reputation doesn't allow it just yet. I hope you look into MVVM as a future architectural pattern as others have suggested and if you have any further question beyond my simple overview feel free to contact me. Good Luck!
Edit: the classes for the base view model, commands, and binding properties come from the MVVM Light library. You can import this into your project from visual studio using: Tools -> NuGet Package Manager -> Manage NuGet Packages For Solution -> search for "MVVM Light"
Edit 2
For an example related to your comment. We have a parent grid containing an editor view that is always at 70 percent of max window size and a binding for our extended control panel size:
View:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="{Binding Width}"/>
</Grid.ColumnDefinitions>
<ContentPresenter Content="{Binding ViewManager.EditorControlView}" Grid.Column="0"/>
<ContentPresenter Content="{Binding ViewManager.ExtendedControlView}" Grid.Column="1"/>
</Grid>
Binding:
public string Width
{
get { return _width; }
set
{
_width = value;
RaisePropertyChanged("Width")
}
}
//set our extended control to the other 30 percent of the window size
Width = "3*";
To properly change this width adhering to MVVM standards, we need to use some form of communication between our view models and luckily that is another lesson you can pick up on from here. Just because iv used MVVM Light throughout this example id recommend searching google for "MVVM Light Messenger" as it provides a solid way of doing this communication. Using this you can just raise a change width to hide other window components in the parent view from anywhere in your application. :D

Why not create a Usercontrol with a controltemplate that will change upon some property its template. A ControlTemplate with Triggers

What you are looking is an Accordian Control. These are easy to implement, but it can take a little bit of work to make then look/animate nicely. HERE is an open source project with an example and docs. This duplicate question (by which I mean yours is the duplicate, the other came first) also provides a variety of solutions.
Addressing the second component of your question- how to accommodate a small screen- there are several good design guidelines out there. You could look at the Apple Watch Human Interface Guidelines (that's certainly a small screen), or you could ask for recommendations in the UX/UI Stack Exchange.

Related

Xamarin MVVM: updating control styling (font size, border color etc.) by some calculation results done in ViewModel

I'm making first steps in learning Xamarin Forms and run into design question which can't find easy solution for. I want to utilize MVVM approach and implement it with best practices in mind, though I realize same thing could be done in different ways.
So I have Button and Label controls in View. Clicking button sends Command to ViewModel that runs some calculations (or delegates them to the model) and impacts text and visual representation of the Label in the text field. ViewModel has Text property to display in the textbox which could easily to be done with property bindings
<Label Text="Binding Text" ...></Label>
so once ViewModel modifies the text it's shown on a View updated, it's not a problem. The design in question is second part: I'm not sure how to update visual styling for the label, like border color and strikethrough style of font right after calculation is done in ViewModel, it's something that I believe only codebehind (xaml.cs) could properly do, not binding.
One of the approach would be to leave it as a command xaml -> VM -> M
<Button ButtonClickedCommand="{Binding RunCalculationsCommand}" ...>
and have VM to raise a certain ad-hoc event in ViewModel that codebehind catches and does it "styling" job: VM -> xaml.cs
Another is to have a regular event (not Command) raised on view that starts calculation in ViewModel and runs styling xaml -> xaml.cs -> VM -> M
<Button Clicked="RunCalculations_OnClicked" ...>
But then it's more tightly coupled and does not fully follows MVVM per my understanding.
Or I guess the best one could be something else that I'm not yet aware of
Two options: bind a trigger, or bind the control properties directly to VM.
Then, update VM properties in your button command.
Bind a trigger with preset styles.
<Label Text="{Binding Text}" TextColor="Green">
<Label.Triggers>
<DataTrigger
Binding="{Binding CalculationResultIsNegative}"
TargetType="Label"
Value="true">
<!-- set Specific Property -->
<Setter Property="TextColor" Value="Red" />
<!-- or set Specific Style in App.xaml -->
<Setter Property="Style" Value="{StaticResource RedStyleDefinedInAppXaml}" />
</DataTrigger>
</Label.Triggers>
</Label>
Update in your VM
void RunCalculationsCommandMethod ()
{
//if...
CalculationResultIsNegative = true;
//else..
//CalculationResultIsNegative = false;
}
Bind control properties in XAML
<Label Text="{Binding Text}"
TextColor="{Binding ColorPropertyFromVM"}
FontAttributes="{Binding FontAttributesProperty"} />
Update in your VM
void RunCalculationsCommandMethod ()
{
//if...
ColorPropertyFromVM = Color.Red;
FontAttributesProperty = FontAttributes.Bold;
}

Is there a way of using virtualization with hidden panels or expanders?

I'm trying to improve performance with my WPF application and I'm having problems with a complex ItemsControl. Although I've added Virtualization, there's still a performance problem and I think I've worked out why.
Each item contains a series of expandable areas. So the user sees a summary at the start but can drill down by expanding to see more information. Here's how it looks:
As you can see, there's some nested ItemsControls. So each of the top level items has a bunch of Hidden controls. The virtualization prevents off-screen items from loading, but not the hidden items within the items themselves. As a result, the relatively simple initial layout takes a significant time. Flicking around some of these views, 87% of time is spent parsing and Layout, and it takes a few seconds to load.
I'd much rather have it take 200ms to expand when (if!) the user decides to, rather than 2s to load the page as a whole.
Asking for advice really. I can't think of a nice way of adding the controls using MVVM however. Is there any expander, or visibility based virtualization supported in WPF or would I be creating my own implementation?
The 87% figure comes from the diagnostics:
If you simply have
- Expander
Container
some bindings
- Expander
Container
some bindings
+ Expander
+ Expander
... invisible items
Then yes, Container and all bindings are initialized at the moment when view is displayed (and ItemsControl creates ContentPresenter for visible items).
If you want to virtualize content of Expander when it's collapsed, then you can use data-templating
public ObservableCollection<Item> Items = ... // bind ItemsControl.ItemsSource to this
class Item : INotifyPropertyChanged
{
bool _isExpanded;
public bool IsExpanded // bind Expander.IsExpanded to this
{
get { return _isExpanded; }
set
{
Data = value ? new SubItem(this) : null;
OnPropertyChanged(nameof(Data));
}
}
public object Data {get; private set;} // bind item Content to this
}
public SubItem: INotifyPropertyChanged { ... }
I hope there is no need to explain how to to do data-templating of SubItem in xaml.
If you do that then initially Data == null and nothing except Expander is loaded. As soon as it's expanded (by user or programmatically) view will create visuals.
I thought I'd put the details of the solution, which is pretty much a direct implementation of Sinatr's answer.
I used a content control, with a very simple data template selector. The template selector simply checks if the content item is null, and chooses between two data templates:
public class VirtualizationNullTemplateSelector : DataTemplateSelector
{
public DataTemplate NullTemplate { get; set; }
public DataTemplate Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null)
{
return NullTemplate;
}
else
{
return Template;
}
}
}
The reason for this is that the ContentControl I used still lays out the data template even if the content is null. So I set these two templates in the xaml:
<ContentControl Content="{Binding VirtualizedViewModel}" Grid.Row="1" Grid.ColumnSpan="2" ><!--Visibility="{Binding Expanded}"-->
<ContentControl.Resources>
<DataTemplate x:Key="Template">
<StackPanel>
...complex layout that isn't often seen...
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="NullTemplate"/>
</ContentControl.Resources>
<ContentControl.ContentTemplateSelector>
<Helpers:VirtualizationNullTemplateSelector Template="{StaticResource Template}" NullTemplate="{StaticResource NullTemplate}"/>
</ContentControl.ContentTemplateSelector>
</ContentControl>
Finally, rather than using a whole new class for a sub-item, it's pretty simple to create a "VirtualizedViewModel" object in your view model that references "this":
private bool expanded;
public bool Expanded
{
get { return expanded; }
set
{
if (expanded != value)
{
expanded = value;
NotifyOfPropertyChange(() => VirtualizedViewModel);
NotifyOfPropertyChange(() => Expanded);
}
}
}
public MyViewModel VirtualizedViewModel
{
get
{
if (Expanded)
{
return this;
}
else
{
return null;
}
}
}
I've reduced the 2-3s loading time by about by about 75% and it seems much more reasonable now.
This simple solution helped me:
<Expander x:Name="exp1">
<Expander.Header>
...
</Expander.Header>
<StackPanel
Margin="10,0,0,0"
Visibility="{Binding ElementName=exp1, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
<Expander x:Name="exp2">
<Expander.Header>
...
</Expander.Header>
<StackPanel
Margin="10,0,0,0"
Visibility="{Binding ElementName=exp2, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
An easier way to achieve this is to change the default Visibility of the contents to Collapsed. In this case WPF won't create it initially, but only when a Trigger sets it to Visible:
<Trigger Property="IsExpanded" Value="true">
<Setter Property="Visibility"Value="Visible" TargetName="ExpandSite"/>
</Trigger>
Here "ExpandSite" is the ContentPresenter within the default ControlTemplate of the Expander control.
Note that this has been fixed in .NET - see the default style from the WPF sources on github.
In case you have an older version, you can still use this fixed control template to update the old one with an implicit style.
You can apply the same technique to any other panel or control.
It's easy to check if the control was already created with Snoop. Once you attached it to your application, you can filter the visual tree with the textbox on the top left. If you don't find one control in the tree, it means it was not created yet.

UI Implementation, best route to take

I'm working on a small application that I need some assistance with implementing.
The gist is I would like to create a grid-like container that houses a dynamic number of the identical column-like structures. Inside each column-like structure is a few text fields and radio buttons that the user can interact with.
I've been looking into some different WPF objects that may be of some help, but I'm pretty overwhelmed.
Some things I've thought of:
The column-like structure can be a custom built UserControl. This UserControl will have all the logic to deal with the interactions of the various buttons and text fields.
We can use a StackPanel, set to Horizontal, to house these UserControls. From what I've gathered, a StackPanel seems like it may be the perfect container for my purpose.
Some questions I have:
Will I need to create a .xaml for the UserControl?
In the event that more UserControls that can be displayed are added, does the StackPanel provide a way to scroll from left to right with a horizontal scroll bar?
Do I need to custom define the size of UserControl, or is it possible to just specify a set width and use the height of the StackPanel?
Is there an easier or more appropriate solution?
Lastly, I've included a very rough sketch to provide a visual idea of what I'm looking to do:
I will try to answer your questions:
You shall definitely extract a control for each part of your control which is used more than once.
A stack panel is a container, is similar to a div in HTML. If you want to show a scroll, you better use ScrollViewer instead of Stackpanel.
If the height of the StackPanel/ScrollViewer is set, controls inside them, will respect that limit unless you actually set an explicit height for the child elements. If the height of the Stackpanel/ScrollViewer is not set, the children elements will be stretched (so, in order to fit into the parent, you have to specify a height). You can decide, however, how you want to do it.
I think that is a god solution to extract a control for each redundant part and use a ScrollViewer!
If something is unclear, please let me know! Good luck :)
I would start by reading a bit about the MVVM pattern which plays well with WPF. Other than that I would use a ListView instead of a horizontal stackpanel and a scrollviewer. I Would prefer my data to expand vertically than horizontally. Here's some sample code to use.
Here's your model class:
public enum CustomOption
{
Option1,
Option2,
Option3
}
public class Item
{
public string Value1 { get; set; }
public string Value2 { get; set; }
public string Value3 { get; set; }
CustomOption Option { get; set; }
}
Here's your ViewModel:
public class MainWindowVM
{
public ObservableCollection<Item> Items { get; set; }
}
And here's your MainWindow:
<ScrollViewer
<ListView ItemsSource="{Binding Items}">
<ListView.Resources>
<ObjectDataProvider x:Key="EnumValues" MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:CustomOption"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Value1" Margin="5"/>
<TextBox Text="{Binding Value1}" Margin="2"/>
<TextBlock Text="Value2" Margin="5"/>
<TextBox Text="{Binding Value2}" Margin="2"/>
<TextBlock Text="Value3" Margin="5"/>
<TextBox Text="{Binding Value3}" Margin="2"/>
<TextBlock Text="Option" Margin="5"/>
<ComboBox ItemsSource="{Binding Source ={StaticResource EnumValues}}" SelectedItem="{Binding Option}" Margin="5"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
This is a very simplistic example to get you started. You can play around to get a much better looking style and layout. I used a ComboBox instead of RadioButtons which I dislike. You also need to set the DataContext of your MainWindow as the MainWindowVM somewhere. You can do that when your application starts for now.

Visual Studio Designer shows empty Window when using custom ContentPropertyAttribute

My application has a lot of windows and most of them share some basic features. Because of that I extended the Window class to create a base for all my windows.
Everything compiles and displays fine but the designer just shows an empty window when I use my window class.
I made a basic example that can be easily used, my real window is much more complex but this shows the problem.
Here is the code:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Markup;
namespace WpfApplication1
{
[ContentProperty("ContentElement")]
public class MyWindow : Window
{
public ToolBar ToolBar { get; private set; }
public StatusBar StatusBar { get; private set; }
public Border ContentBorder { get; private set; }
public UIElement ContentElement
{
get { return (UIElement)GetValue(ContentElementProperty); }
set { SetValue(ContentElementProperty, value); }
}
public static readonly DependencyProperty ContentElementProperty = DependencyProperty.Register(
"ContentElement", typeof(UIElement), typeof(MyWindow),
new PropertyMetadata(null, (d, e) =>
{
MyWindow w = (MyWindow)d;
w.ContentBorder.Child = (UIElement)e.NewValue;
}));
public MyWindow() : base()
{
ToolBar = new ToolBar();
ToolBar.Height = 30;
ToolBar.VerticalAlignment = VerticalAlignment.Top;
StatusBar = new StatusBar();
StatusBar.Height = 20;
StatusBar.VerticalAlignment = VerticalAlignment.Bottom;
ContentBorder = new Border();
ContentBorder.SetValue(MarginProperty, new Thickness(0, 30, 0, 20));
Grid grid = new Grid();
grid.Children.Add(ToolBar);
grid.Children.Add(ContentBorder);
grid.Children.Add(StatusBar);
Content = grid;
}
}
}
XAML example for using MyWindow:
<local:MyWindow x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300">
<Grid>
<Rectangle Fill="Blue" />
</Grid>
</local:MyWindow>
Doing the exact same thing with a UserControl works just fine, also in the designer. Just replace every occurance of MyWindow with MyUserControl and extend from UserControl if you want to try that.
Is there any way I can get a custom Window like that to work with the designer, or do i have to make a UserControl and use that in every window?
Also, is this some kind of bug or intended behavior?
Addional info: I'm running Visual Studio 2015 Community and I'm using .net 4.6
I Also tried another approach. Instead of using the ContentPropertyAttribute i have overwritten the ContentProperty like this:
new public object Content {
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
new public static DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(BaseUserControl), new PropertyMetadata(null, (s, e) =>
{
MyWindow bw = (MyWindow)s;
bw.ContentBorder.Child = (UIElement)e.NewValue;
}));
Again this works completely fine with a UserControl. With a Window I can at least see the Content in the designer now, but the ToolBar and StatusBar are still not showing up in the designer. When running it everything works correctly.
First, I am no super expert on WPF, but have done a bunch and think I can offer and help clarify some components. First, you can NOT derive from a .XAML based declaration of a WPF-Window, it can only be if entirely within code. I have come to find that sometimes the visual element building is much easier to do in XAML than it is within code, but both can and do work.
So, that said, I would like to offer a solution that might work for you. Starting with WPF Window Style / Templatea, if you are not already familiar with them, along with other controls you can run through their defaults.
First, I am starting with a RESOURCE DICTIONARY STYLE definition that will mimic much of what you may want in your default form. This becomes the stuff within the "ControlTemplate" of the style definition. I have created this as a file "MyWindowStyle.xaml" at the root level WpfApplication1 I created on my machine (just to match your sample project file namespace reference).
Inside the template, you could have almost anything... grids, dock panel, stack panels, etc. In this case, I have used a DockPanel and added your sample ToolBar, StatusBar and two extra labels just for sample. I also preset size and bogus color just to give visualization of the parts when you confirm their impact.
The CRITICAL element to look at is the . This identifies where the content for each of your DERIVED Windows content will be placed... Think of it as a place-holder for each one of your forms for individuality while the rest of the form, its controls all remain consistent. You will see it come into play as you play around with it.
The content of it is and notice the style x:Key="MyWindowStyle". This coincidentally is the same as the xaml, but you could have 100's of styles within a single resource dictionary. I am keeping simple to just the one for demo purposes.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Style x:Key="MyWindowStyle" TargetType="Window">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Grid>
<Grid.Background>
<SolidColorBrush Color="{DynamicResource WindowColor}"/>
</Grid.Background>
<AdornerDecorator>
<DockPanel LastChildFill="True" Background="Blue">
<!-- List items docked to the top based on top-most first going down -->
<ToolBar x:Name="tmpToolBar" Height="45" DockPanel.Dock="Top" />
<Label Content="Testing by Style"
Height="30" Width="150" DockPanel.Dock="Top"/>
<!-- When docking to the bottom, start with bottom most working up -->
<StatusBar x:Name="tmpStatusBar" Height="30"
Background="Yellow" DockPanel.Dock="Bottom" />
<Label Content="Footer area based from style"
Height="30" Width="250" DockPanel.Dock="Bottom" />
<!-- This one, since docked last is rest of the space of the window -->
<ContentPresenter DockPanel.Dock="Bottom"/>
</DockPanel>
</AdornerDecorator>
<ResizeGrip x:Name="WindowResizeGrip"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Visibility="Collapsed"
IsTabStop="false" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ResizeMode" Value="CanResizeWithGrip">
<Setter TargetName="WindowResizeGrip"
Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Next, we need to make this publicly available for the entire duration of the application, including availability within the designer mode... Within your projects "App.xaml" which is the startup for the application, it will have a default and empty area. Replace it with this.
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/WpfApplication1;component/MyWindowStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Now, to a CODE-ONLY (not a .xaml window based definition) of your "MyWindow.cs" class. If you look at the style where I declared the toolbar and statusbar, I assigned them the names of "tmpToolBar" and "tmpStatusBar" respectively. Notice the [TemplatePart()] declarations. I am now expecting the template to HAVE these controls by the given name within the TEMPLATE somewhere.
Within the constructor, I am loading the Style from the App.xaml resource dictionary being fully available. Then I follow-up with the OnApplyTemplate() which I typically heavily document my code so anyone following me has some idea how / where things originated from and self explanatory.
My entire "MyClass.cs" is below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace WpfApplication1
{
[TemplatePart(Name = "tmpToolBar", Type = typeof(ToolBar))]
[TemplatePart(Name = "tmpStatusBar", Type = typeof(StatusBar))]
public class MyWindow : Window
{
protected ToolBar myToolBar;
protected StatusBar myStatusBar;
public MyWindow() : base()
{
// NOW, look for the resource of "MyWindowStyle" within the dictionary
var tryStyle = FindResource("MyWindowStyle") as Style;
// if a valid find and it IS of type Style, set the style of
// the form to this pre-defined format and all it's content
if (tryStyle is Style)
Style = tryStyle;
}
// the actual template is not applied until some time after initialization.
// at that point, we can then look to grab object references to the controls
// you have need to "hook up" to.
public override void OnApplyTemplate()
{
// first allow default to happen
base.OnApplyTemplate();
// while we get the style loaded, we can now look at the expected template "parts"
// as declared at the top of this class. Specifically looking for the TEMPLATE
// declaration by the name "tmpToolBar" and "tmpStatusBar" respectively.
// get object pointer to the template as defined in the style template
// Now, store those object references into YOUR Window object reference of Toolbar
var myToolBar = Template.FindName("tmpToolBar", this) as ToolBar;
if (myToolBar != null)
// if you wanted to add your own hooks to the toolbar control
// that is declared in the template
myToolBar.PreviewMouseDoubleClick += myToolBar_PreviewMouseDoubleClick;
// get object pointer to the template as defined in the style template
var myStatusBar = Template.FindName("tmpStatusBar", this) as StatusBar;
if (myStatusBar != null)
myStatusBar.MouseDoubleClick += myStatusBar_MouseDoubleClick;
// Now, you can do whatever else you need with these controls downstream to the
// rest of your derived window controls
}
void myToolBar_PreviewMouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// in case you wanted to do something based on PreviewMouseDoubleClick of the toolbar
MessageBox.Show("ToolBar: Current Window Class: " + this.ToString());
}
void myStatusBar_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// in case something for MouseDoubleClick on the StatusBar
MessageBox.Show("StatusBar: Current Window Class: " + this.ToString());
}
}
}
So now, lets put it into place. Have your application's main window derive from the MyWindow class. The only thing you need there is
namespace WpfApplication1
{
public partial class MainWindow : MyWindow
{}
}
In the DESIGNER of your form, put in a few controls, such as label, textbox, whatever. You do not see your actual other style yet, but just go with it. Save and run the sample app. Your main window should be displayed with the entire pre-defined template there ALONG WITH the few extra control you had placed specifically on this form.
Now, to get the full visualization in your "MainWindow" from the designer perspective. Within the .xaml area of
<my:MyWindow
x:Class="WpfApplication1.MainWindow"
[other declarations] >
just add the following before the close ">"
Style="{StaticResource MyWindowStyle}"
The resource is available via the App.xaml at startup and you should now be able to see the entire visual while you are designing... but you cant change the outermost template, just the content specific to this one page as mentioned about the "ContentPresenter" part of the template definition. What you are changing is within the place-holder portion allocated by the Template. If you want to change the main part of the window controls, you need to update the TEMPLATE!
But here is part of the trick of the template designer. Start with this, and build in what you need visually, get it placed right and once ready, take it out of here and put into the template and now it is applicable to the rest of all windows. Fix fonts, sizes, colors, whatever.
Hope this helps, and if you have any questions for follow-up, let me know.
Window class is very complex in compare to UserControl class. Microsoft has written more than 8k lines of code in Window class compare to 80 lines in UserControl, additionally Window class contain many operation/event/restriction on content property, and any one piece of code is hindering in rendering the content when you use [ContentProperty("ContentElement")] with the Window subclass MyWindow .
Probably making it a UserControl is better option, If not possible you can write some code temporarily(copy code from ContentElement property) in content property to see the design view.
<lib:MyWindow.Content>
<Button Content="Click" Width="200" />
</lib:MyWindow.Content>
and then just remove the code before run time. (Not a good idea but, whatever works.:) and I suspect that you have already figured that out.

Programmatically moving TextBlocks in WPF

I have been reading some tutorials on XAML but it does not help me. I have an empty application window and I need to create 30 TextBoxes in 3 rows.
Being used on the win forms, I thought I would figure it out - well, I did not. I cannot seem to find a way how to create them on certain coordinates.
You first want to place a Canvas control on your screen, then you can populate it with TextBoxes placed at whatever Canvas.Left and Canvas.Top position you want.
That said though, WPF has a much better layout/arrangement system than WinForms, and trying to use it like it's WinForms means you'll miss out on a lot of what makes WPF so great, and you'll be making things a lot harder on yourself.
The WPF way of doing the same thing would be to use an ItemsControl, and a collection of objects that each contain data that the UI needs to to know for display purposes.
First you would create a class to represent each TextBox
public class MyClass
{
public string Text { get; set; }
public int X { get; set; }
public int Y { get; set; }
}
Note: This class should implement INotifyPropertyChanged if you want to change the properties at runtime and have the UI automatically update.
Then make a list of this class, and bind it to an ItemsControl
<ItemsControl ItemsSource="{Binding ListOfMyClass}" />
Then you'd want to overwrite the ItemsPanelTemplate to be a Canvas (the best WPF panel for positioning items according to an X,Y position)
<ItemsControl ItemsSource="{Binding ListOfMyClass}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Next overwrite the ItemTemplate to draw each item using a TextBlock
<!-- ItemTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
And add an ItemContainerStyle that binds Canvas.Left and Canvas.Top properties to X,Y properties on your object
<!-- ItemContainerStyle -->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
And this will take a List of MyClass objects, and render them to the screen inside a Canvas, with each item positioned at the specified X,Y coordinates.
With all that being said, are you sure this is what you want? WPF has much better layout panels than WinForms, and you don't have to position every element according to an X,Y coordinate if you don't want to.
For a quick visual introduction of WPF's Layouts, I'd recommend this link : WPF Layouts - A Visual Quick Start
Also since it sounds like you're new to WPF and coming from a WinForms background, you may find this answer to a related question useful : Transitioning from Windows Forms to WPF
WPF layout involves choosing a layout container and placing your controls in it. There are several different containers:
The Grid container is a powerful tool for laying out your form in rows and columns. You have complete control over the size of each cell, and you can have rows or columns "span" each other.
The DockPanel container allows you to "dock" controls to the edges of your window or the center. You'd use it to layout a window with smart icon bars, ribbons, status windows, and toolboxes, like Visual Studio itself.
The StackPanel container can be used to stack controls either on top of each other or next to each other
The UniformGrid container is a less powerful version of the container that keeps all cells the same size.
The Canvas container allows you to specify the X & Y coordinates of your controls.
There are one or two others but these are the ones I've used.
The bad thing about laying out a form using X & Y coordinates is that the form does not handle resizing well. This can be exacerbated when you support globalization, as the labels and such for a string may be a lot longer in a foreign language. The best example off the top of my head is Spanish. A lot of English phrases, when translated to Spanish, are a lot longer.
The Grid container gives you the most control over layout. Columns can automatically size themselves to the longest string in the column, while the rest of the columns adjust themselves as necessary, again automatically. You don't have to write one line of code to get that effect; it's all there in the Grid control out of the box.
If you insist on laying out your form the Winforms way, use a Canvas. But you're not going to get the benefit of using the more advanced layout facilities in the other containers, especially the Grid control. I use that almost exclusively in my forms.
EDIT
Using layout controls other than Canvas means that you think about layout differently in WPF than in WinForms. You work at a higher conceptual level and leave the details about figuring out where on the screen a particular control will be displayed to WPF. You also don't have things like the WinForms Anchor property in WPF, which always seemed kind of a hack to me.
The WPF was designed to offer a power and rich framework for designer which make it a different from the classic winforms. You can achieve what want by adding your TextBox control to a canvas and changing the attached property following is a full example illustrating this:
MainWindow
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Canvas Name="mainCanvas" Margin="31,-10,-31,10">
<TextBox Name="myTextBox" Canvas.Left="131" Canvas.Top="109" Height="84" Width="135"></TextBox>
<Button Content="Button" Height="62" Canvas.Left="271" Canvas.Top="69" Width="91" Click="Button_Click"/>
</Canvas>
</Grid>
</Window>
Code Behind
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
myTextBox.SetValue(Canvas.LeftProperty,(double)myTextBox.GetValue(Canvas.LeftProperty)+50.0);
}
}
}
If you want to position the TextBoxes in a grid-way, use Grid:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
...
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" />
<TextBox Grid.Row="0" Grid.Column="1" />
<TextBox Grid.Row="0" Grid.Column="2" />
...
<TextBox Grid.Row="1" Grid.Column="0" />
<TextBox Grid.Row="1" Grid.Column="1" />
<TextBox Grid.Row="1" Grid.Column="2" />
...
</Grid>

Categories