Finding a better way to write complex WPF UIs - wpf

The WPF application I've inherited contains a significant amount of XAML which follows a pattern like:
<Window ...>
<Grid>
<z:SomeUserControl>
<z:AnotherUc>
<Label /> <Button /> <ComboBox />
</z:AnotherUc>
<z:AnotherUc>
<Label /> <Button /> <ComboBox />
</z:AnotherUc>
</z:SomeUserControl>
</Grid>
</Window>
In other words, we have UI sections grouped by UserControl, often nested inside other UserControls. At some point, the content is defined using basic WPF content controls.
The problem we're trying to cope with is that the x:Name attribute cannot be applied to any of the inner most controls due to the infamous WPF limitation:
Cannot set Name attribute value {0} on element {1}. {1} is under the scope of element {2}, which already had a name registered when it was defined in another scope
This presents a problem because the code-behind needs to be able to reference elements within the UserControls. UserControls were selected to group parts of the UI because all the styling and templating of default controls because too unwielding and the markup quickly turned in to a horrible, unreadable mess.
However, if Microsoft has no intention of resolving this so-called "limitation," a better way must be found. Considering has been placed in using CS + external XAML template file as seen in GaryGJohnson's work-around on the connect site. However this has a feeling of sphagetti and anything that interrupts bindings is a no-go.

The best option here is typically to avoid using code behind on those "inner" elements. You can still use data binding, so binding these to properties in your DataContext will work properly.
The other alternative, of course, is to expose the Label/Button/ComboBox directly as Dependency Properties of the AnotherUc class. This would allow you to usen them directly as members of the UserControl, which avoids the scoping issue.
The downside to this, of course, is that you must customize the user control for a specific combination of elements, instead of allowing any controls to be placed within it.

Sounds like a mess by design. You can create your own AttachedProperty - MyNameProperty, set it in your XAML and write your own Logical/Visual tree helper, which'll work of it. I'm not saying I would do that, but if you need a quick workaround (w/out radical redesign of you UI composition model), that may do you.

Related

Using a UserControl type vs a control's concrete type

Okay, I understand what a UserControl is and how they can be shared across multiple Windows/Views to share functionality, etc. Though, what is the benefit of using a UserControl type? It seems a lot cleaner if you were to use the root control instead of wrapping it inside a UserControl.
Example:
<UserControl>
<Grid>
//...
<Grid/>
</UserControl>
vs.
<Grid>
//...
</Grid>
Using the root control also has the benefit if reducing the VisualTree.
UserControl has some properties it inherits from ContentControl, like ContentTemplate, ContentTemplateSelector, some other stuff. If you don't need those, you can create a user control, change the outermost element to Grid and change its base class to Grid in the .xaml.cs, and it'll compile. At least with the trivial example I just tried, it works fine.
However, unless you've identified some concrete problem being created by UserControl in your application, I can't see any reason to go to the trouble. But go ahead, if you like doing things that way.

Reusing User Controls

I'm trying to alter an existing WPF application and my lack of WPF experience (I'm WINFORMS) is making this difficult.
I've come across a situation where I need to reuse a UserControl and I'm not sure how to do this in terms of modifying the xaml. I'll explain.
UserControlA has the following code:
<Grid Name="gdMain" Style="{StaticResource ContentRoot}">
<content:MonitorAlarmsPage />
</Grid>
Now, "MonitorAlarmsPage" is an AXML document that defines another UserControl - UserControlB. This UserControl, once created has to persist for the lifetime of the application.
So, I could have many UserControlAs, but only ONE UserControlB.
I've created a static class that has an appropriate UserControlB field which is updated when UserControlB is created, but how do I modify the content:MonitorAlarmsPage so that the content of the grid is replaced by the existing UserControlB as referenced in this static class and not by the XAML file that defines UserControlB? Can this actually be done? i.e. essentially, insert pre created user controls inside an XAML page.
To make things a bit clearer, UserControlB is essentially a page that can sit inside another page. The page is complex and there is a massive overhead incurred when it is created and so must only be created once.
WPF is very different to WinForms, so you certainly have your work cut out for you. The basic idea for the solution to your problem is this. You'll need to add a DependencyProperty to the MonitorAlarmsPage control. This will enable you to data bind to this property from outside the control.. This property should be of a type of custom class that you define, that contains all of the properties required in the inner control.
The next stage is to develop a DataTemplate that defines how WPF should display your custom class when it comes across an instance of it. In this DataTemplate, you declare your inner control, so that when WPF sees the custom class, it displays the inner control and sets the custom class as the DataContext for the inner control:
<DataTemplate DataType="{x:Type YourXmlNamespacePrefix:YourCustomClass}">
<YourControlsXmlNamespacePrefix:YourInnerControl DataContext="{Binding}" />
</DataTemplate>
Finally, you'll need to data bind your custom class (the static class) to the outer UserControl:
<content:MonitorAlarmsPage YourCustomClassProperty="{Binding YourStaticClass}" />
So just to be clear... this static class should be a data class, not a UI class. When using WPF, we manipulate data, not UI elements. We let the wonderful templating system generate the UI for us.

What is the difference between x:Reference and ElementName?

According to the x:Reference Markup Extension page on MSDN, x:Reference
References an instance that is declared elsewhere in XAML markup. The reference refers to an element's x:Name.
According to the Binding.ElementName Property page on MSDN, ElementName
The value of the Name property or x:Name Directive of the element of interest.
Looking back at the remarks section on the first page:
x:Reference and WPF
In WPF and XAML 2006, element references are addressed by the framework-level feature of ElementName binding. For most WPF applications and scenarios, ElementName binding should still be used. Exceptions to this general guidance might include cases where there are data context or other scoping considerations that make data binding impractical and where markup compilation is not involved.
For completeness, here is part of the remarks section on the ElementName page:
This property is useful when you want to bind to the property of another element in your application. For example, if you want to use a Slider to control the height of another control in your application, or if you want to bind the Content of your control to the SelectedValue property of your ListBox control.
Now, while I am fully aware of when and how to use the ElementName property, I don't fully understand the difference between it and the x:Reference markup extension. Can anybody please explain this and in particular, expand on the last sentence shown from the x:Reference remarks section?:
Exceptions to this general guidance might include cases where there are data context or other scoping considerations that make data binding impractical and where markup compilation is not involved.
Basically like you said those two do almost the same. However there are small differences under the hood.
{x:Reference ...} -> returns just a reference of an object it doesn't create that "bridge" between two properties like binding would do. Behind all that a service is being used that searches for the given name in a specific scope which is usually the window itself.
{Binding ElementName="..." } -> first of all it creates that binding object then it searches for the object name but not by using the same technique under the hood as x:Reference. The search algorithm moves up and/or down in VisualTree to find the desired element. Therefore a functional VisualTree is always needed. As example when used inside a Non-UiElement, it won't work. In the end the Binding stays and does its daily bread.
This won't work:
<StackPanel>
<Button x:name="bttn1" Visibility="Hidden">Click me</Button>
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Visibility="{Binding ElementName=bttn1, Path=DataContext.Visibility}"/>
....
This works:
<StackPanel>
<Button x:name="bttn1" Visibility="Hidden">Click me</Button>
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Visibility="{Binding Source={x:Reference bttn1}, Path=DataContext.Visibility}"/>
....
Sort of like that :)
ElementName is platform specific. I.e. it may or may not be present based on which platform you're using. x:Reference elevates that concept to a XAML native feature. Thus any platform that supports XAML supports x:Reference.

Linking View to ViewModel using DataTemplate for a WPF Application using the MVVM pattern

Current I have some Views linked to ViewModels using code similar to the following:
<Application.Resources>
<DataTemplate DataType="{ x:Type vm:AgeIndicatorViewModel}">
<v:AgeIndicatorView />
</DataTemplate>
</Application.Resources>
I have two questions regarding this:
Does this method allow me to only link one View to each View Model (I think it does improse this limitation on me, but want to be sure)
When using this method, where should I put all of my DataTemplate declarations? At the moment there are only a few, and they are all in App.Xaml - Is there a better location for these, or is App.Xaml fine / Best location?
The most important question is the second really, as at the moment I want to link my ViewModel to my View in this way, as it requires no external libraries etc.
The way my ViewModels are setup, with their Properties and Commands etc is all working already.
Does this method allow me to only link one View to each View Model (I think it does improse this limitation on me, but want to be sure)
Yes. If you're trying to link multiple ViewModels to multiple Views, you need to encapsulate them within a separate VM, and add a new DataTemplate.
When using this method, where should I put all of my DataTemplate declarations? At the moment there are only a few, and they are all in App.Xaml - Is there a better location for these, or is App.Xaml fine / Best location?
App.Xaml is fine, or really any place in the visual hierarchy above where the DataTemplate will be used.
That being said, if the project gets to be a very large scale project, it's often nicer to start using Merged Resource Dictionaries - this allows you to setup resource dictionaries "near" where you define the View/ViewModel pairs, but then use them at a higher level (ie: merge them into App.Xaml).
Specifying the implicit DataTemplate like you do in your question does tie your View-Model to a single View. You can override this at any control level though, so you could have:
<Window.Resources>
<DataTemplate DataType="{x:Type vm:AgeIndicatorViewModel}">
<v:AgeIndicatorView2 />
</DataTemplate>
</Window.Resources>
This would change the view applied to a view-model for the given window. This can be done on any control at any level.
The benefit of doing this at the app-level though is that it is applied across all windows in your application. While my example above would only be applied to a single window.
In general, the app resources is the best place to define these. Since if you have multiple Windows (i.e. Window1 and Window2), then your view-model will always pick up your implicit DataTemplate.

Creating instances of resources?

I'm brand spanking new to WPF and am trying to play around with projects to better understand what I'm reading.
My understanding of a resource is that it is the instance, you can't use it like a factory and create instances of it. For example, a XAML-defined rectangle. You can reference it, but you can't have numerous instances of it all over the surface.
In WPF, what would be the way to do that? If I define a Rectangle as a resource with specific properties and wanted to have multiple instances of that within a dynamically-generated grid, how should I be going about it? Or is there a different way I should be trying to do this?
Purely academic exercise with no real-world application.
Actually there's nothing about resources in particular that prevents you from using it multiple times. A perfect example of this is brush resources, style resources, etc. You define them in XAML and the XAML parser creates a single instance of the resources and stores them in the resource dictionary and these brushes, styles, etc can be used as property values many times even though only a single instance of the resource was created.
But having said that, as you noted, you can't really define a Rectangle resource and use it multiple times in the visual tree. This has nothing to do with the fact that it's a resource, but rather it has to do with the fact that a FrameworkElement cannot be a child of more than one parent element.
So what we have instead is called "templates". These tell WPF how to create an element tree but does not actually create the tree until you instantiate the template. Below is an example.
<UserControl>
<ItemsControl ItemsSource="{Binding WholeBunchOfItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Rectangle Fill="Yellow" />
<ContentPresenter Content="{Binding}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
In this example I've bound an ItemsControl to a collection of some sort. For each item in the collection, the ItemsControl will use my DataTemplate to render the item. Within a DataTemplate you can use data binding to access the current item.
I would suggest reading up on MSDN about ControlTemplate, DataTemplate, and Style. These are all important concepts in WPF/Silverlight.
To get multiple instances replicated across a grid or listbox, you need to set the data template to define the UI controls for each row of data, and then databind the grid or listbox to a collection of data that determines how many rows and the individual field values.
Key term for you to research first: data template.

Resources