WPF complex binding for custom control with sub-class as settings - c#

Right now I am trying to implement a custom control (which is of course way more complex than the demo solution I attached), and I kind of failed at passing a value down via binding.
First of all, the structure of the control:
Control (this is basically a wrapper which passes down properties and handles common functionalities)
Settings (this is a dependency property of the control and is a custom class as well)
Text (this is a dependency property of the settings class)
_renderer (this is a private field of the control which is responsible for rendering the control => in my real control it's just rendering a part of it, but for this example it's enough to just do it like that).
The goal of this is to pass the text from a control like a textbox or something down to the renderer which gets initialized with a reference to the settings.
The XAML which uses the control is written as follows:
<TextBox Text="Initial Text"
x:Name="TextSource"
Grid.Row="0" />
<local:CustomControl Grid.Row="1">
<local:CustomControl.Settings>
<local:CustomControlSettings Text="{Binding Path=Text, ElementName=TextSource, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
</local:CustomControl.Settings>
</local:CustomControl>
When I use the same XAML, but give the settings a fixed value for the "Text" then everything works as expected, but as soon as I change it to a binding, I don't even get the initial value any more.
Code with is passing down the text:
Passing the settings to the renderer
private void OnLoaded(object sender, RoutedEventArgs e)
{
_renderer = new ControlRenderer();
_renderer.Initialize(_renderArea, Settings);
}
binding the settings text to the renderers internal text dependency property
SetBinding(TextProperty, new Binding{Path = new PropertyPath(CustomControlSettings.TextProperty), Source = settings});
Note: you can uncomment this without any effect, so this should not be the problem if you ask me.
And hereĀ“s a link to the demo solution I created.

After four hours of pain, later I found the solution. The problem is that the settings-object is not a part of the visual tree and therefore the dependency properties are not resolved.
So to be able to do things like this you need to add the settings to the visual tree. I did that by adding it to the canvas children inside of the CustomControl.

Related

How to use Windows 10 style resource in WPF with XAML Islands

I'm using XAML Islands to make my app and I want to use Windows 10 styling in my WPF app like here. For example <TextBlock Text="Header" Style="{StaticResource HeaderTextBlockStyle}"/> would result in:
But this doesn't work in WPF (It does work in UWP without any modifications), my understanding is that XAML Islands should make it possible. When I try to simply add the code above to my xaml file I get the exception
Cannot find resource named 'HeaderTextBlockStyle'. Resource names are case sensitive.
I get the same exception if I add Style="{StaticResource HeaderTextBlockStyle}" to a <xamlhost:WindowsXamlHost> element.
So I tried to add the controls with code so I added this WindowsXamlHost control as a stackpanel:
<xamlhost:WindowsXamlHost InitialTypeName="Windows.UI.Xaml.Controls.StackPanel" ChildChanged="WindowsXamlHost_ChildChanged"/>
And added this method (an event handler that is ran when the control is made. Learned it from this) that handles adding additional controls (a TextBlock) to the StackPanel:
private void WindowsXamlHost_ChildChanged(object sender, EventArgs e)
{
// Get the host control
WindowsXamlHost host = (WindowsXamlHost)sender;
// Get the StackPanel in the host
Windows.UI.Xaml.Controls.StackPanel sp = (Windows.UI.Xaml.Controls.StackPanel)host.Child;
// Make a TextBlock to add to the StackPanel
Windows.UI.Xaml.Controls.TextBlock textBlock = new Windows.UI.Xaml.Controls.TextBlock();
// Set the text of the TextBlock
textBlock.Text = "LockCursorInMonitor";
// Get the style resources, cast them to the appropriate type for XAML Islands and add them to the TextBlock
textBlock.Style = (Windows.UI.Xaml.Style)Application.Current.Resources["HeaderTextBlockStyle"];
// Another way to get resources but this doesn't work too.
//textBlock.Style = (Windows.UI.Xaml.Style)this.FindResource("HeaderTextBlockStyle");
// Add the TextBlock to the stackpanel
sp.Children.Add(textBlock);
}
The Application.Current.Resources["HeaderTextBlockStyle"] way does nothing and doesn't throw an exception.
The this.FindResource("HeaderTextBlockStyle") way throws the next exception:
System.Windows.ResourceReferenceKeyNotFoundException: ''HeaderTextBlockStyle' resource not found.'
So how can I get these style resources in my WPF app?
One way to achieve this is to use the package ModernWPF but then you lose all the benefits of XAML Islands (if there are any. Everything I needed from XAML Islands is in ModernWPF and is easier to implement).
After installing and setting ModernWPF up you can simply use the <TextBlock Text="Header" Style="{StaticResource HeaderTextBlockStyle}"/> way and it just works.

How to use different panels in a grouped list view?

I need to use a different panel for a particular section/group in my ListView. How do I do that (using XAML, C#, or anything)? I already tried using GroupedStyleSelector but it didn't work (I researched about it but it turned out it's not designed for this purpose). Here's my XAML right now:
<ListView ItemsSource="{Binding Source={StaticResource cvs}}">
<ListView.GroupStyle>
<GroupStyle HidesIfEmpty="True">
<GroupStyle.Panel>
<ItemsPanelTemplate>
// I want to change this for a particular group
<uwp:SGStaggeredPanel/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
I'm thinking of subclassing the panel, but the problem is how do I get a reference to the current group?
https://learn.microsoft.com/en-us/windows/communitytoolkit/extensions/listviewbase
The above article talks about a WCT goody that allows you to dynamically change the Tamplate of the item that is about to be rendered, this particular example is a statically expressed extension that simply works as an attached property to a listview and cycles through two different templates
But you can easily extend ListView into a templated control and then more easily have access to the Viewmodel that houses your Itemsource, from then you can go on to change the
private static void ItemTemplateContainerContentChanging(Windows.UI.Xaml.Controls.ListViewBase sender, ContainerContentChangingEventArgs args)
which is where all the magic takes place.
Notation for implementation
Note 0:
if you don't know mvvm and binding, forget you ever read this and go study it up instead.
Note 1:
All child controls that have no explicitly defined Data Context will inherit their parents.
Note 2:
You will be able to Map incoming controls in the aforementioned function by tracking the incoming args.ItemIndex and then cross checking it with the binded source (Observable list etc) that is housed on the underlying datacontext.
Note 3:
To convert this into a tamplated/custom control you will have to pretty much make your own implementation of ListView like this MyListview:ListView
The Dependency properties will have to be converted to conventional ones,
just type 'propdp' and double tap Tab, to bring up the default tamplate.
You will still have to reference all the different DataTamplates from XAML like its shown in the showcase app listed bellow.
Note 4:
Cut the slack off that showcase code, the stretch direction and the zebra stripes for example are not needed in your case.
https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI/Extensions/ListViewBase/ListViewExtensions.cs
this is the exact location of the code piece i talked about, to check it out in action and play with it, Download 'Windows Community Toolkit' from the store, it is in the Extensions section.

why content template of button does not have object?

i am just exploring windows phone runtime apps template. But i am seeing a weird thing.
I have Button defined in Xaml with ContentTemplate set in it. I wanted extract the Image control defined in the ContentTemplate of this button. But it is coming null.
Xaml code :-
<Button x:Name="PlayButton" Click="PlayButton_OnClick">
<Button.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Name="Panel">
<Image x:Name="ControlImg"
Width="100"
/>
<TextBlock Text="text block" />
</StackPanel>
</DataTemplate>
</Button.ContentTemplate>
Here is button Click event :-
private async void PlayButton_OnClick(object sender, RoutedEventArgs e)
{
var btn = sender as Button;
var ct = btn.ContentTemplate; // this part is also not showing controls in it when expending ct at runtime.
var img = btn.FindName("ControlImg") as Image; // coming null
var stckpnl = btn.FindName("Panel") as StackPanel;// coming null
}
Can anybody check this out why is this happening ?
Edit :- I have broken my problem and reach this very bottom simple level and after seeing this i am just not getting why is this happening ?
That is strange behavior. It should have stack Panel and image in control template. As a work around you can use ContentTemplateRoot to get the image and stackpanel. I have test this, it is working.
((StackPanel)btn.ContentTemplateRoot).Children[0] // image
Hope this helps
Edit:
For Details about why FindName is not working see the the Remarks section on on MSDN
. Here is some relevant quotes
Important In order to use the FindName method effectively, you should understand the concept of a XAML namescope, and how a XAML namescope is created at XAML load time and then referenced and possibly modified at run time. For more info see XAML namescopes.
The most common usage of FindName in your Windows Runtime code will be from within the generated InitializeComponent call for a XAML page. In this situation, FindName is invoked only after the XAML page is loaded. InitializeComponent provides the infrastructure such that any object that was instantiated by XAML loading can conveniently be accessed by your code-behind code. You can then reference the objects as a variable that shares the same name as the markup-declared x:Name.
A run-time API such as FindName is working against a run-time object tree of the app as it exists in memory. When part of this object tree is created from templates or run-time loaded XAML, a XAML namescope is typically not contiguous within that object tree. The result is that there might be a named object in the object tree that a given FindName scope cannot find. The discontinuities between XAML namescopes that you might encounter in typical application scenarios are when objects are created by applying a template, or when objects are created by a call to XamlReader.Load and subsequently added to the object tree.
As you are using DataTemplate so the xaml object tree is not contiguous so that is why FindName is failed to find the control from the xaml tree.
hope this explains...

How to create DataTemplate from UserControl instance?

Question
Basically I would like to do the following but it seems that I cannot:
UserControl myControl = new UserControl();
DataTemplate template = new DataTemplate(myControl);
The question: Is it possible to construct a DataTemplate from UserControl instance? If not, are there any other possible solutions?
Real problem
I'm working on a project where majority of UI views are simple static Word-like documents (e.g some text fields and maybe some images, nothing too fancy). Because most of persons working on this project are not coders we have designed very simple in-house markup language for UI generation. An example of markup of simple view is following:
First name: [Person.FirstName]
Last name: [Person.LastName]
Address: [Person.Address.Street], [Person.Address.City]
Now these templates are loaded at runtime and usercontrols are created based on them. In this case one usercontrol would be created and it would contain simply couple of stack panels and text blocks so that resulting control would look a bit like text document. XAML equivalent would be something like:
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="First name: "/>
<TextBlock Text={Binding Person.FirstName}
</StackPanel>
<StackPanel>
...
</StackPanel>
...
</StackPanel>
Then, I started to implement support for lists but couldn't think of a way how to do that. In theory it is simple and I came up with following syntax (+ XAML equivalent):
[List Customers]
First name: [Person.FirstName]
Last name: [Person.LastName]
Address: [Person.Address.Street], [Person.Address.City]
[EndList]
->
<StackPanel>
<ItemsControl ItemsSource="{Binding Customers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
[Insert code from previous XAML example here]
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
But I can't do that because it seems that I cannot construct DataTemplate directly from UserControl instance. There would be no problems if my UserControls were types, but they are not.
One possible solution is that I could bind ItemsControl.Items directly to list of UserControls (instead of binding to ItemsSource and ItemTemplate) but this is sub-optimal in our case for couple of reasons. I would be willing to try some other solutions first!
Update
For clarification: all I have is plain instance of UserControl class which contains the content I need. E.g.
UserControl control = new UserControl();
var panel = new StackPanel();
panel.Children.Add(...);
panel.Children.Add(...);
control.Content = panel;
// How to use that control as ItemTemplate for ItemsControl?
// It seems that it is not possible directly but I want to
// know what my options are.
I don't have class for it because I'm constructing it at run-time and I don't want to create new type dynamically by emiting IL code because it is way too painful.
Creating a datatemplate from Code behind goes like this:
FrameworkElementFactory factory = new FrameworkElementFactory(MyUserControl.GetType());
DataTemplate dt = new DataTemplate();
dt.VisualTree = factory;
yourItemsControlInstance.ItemTemplate = dt;
A datatemplate is a definition of controls to be built at runtime, that is way this construction with a ElementFactory. You do not want the same instance of the UserControl for every item in your ItemsControl.
Ah I understand your problem now. I don't think there is an easy way (one or two lines of code) to create a datatemplate from a UserControl instance.
But to solve your problem I see two directions:
At the point where an usercontrol is created, create a datatemplate instead and use that. It will be cumbersome, with nested FrameworkElementFactories. I have never done that, and the MSDN documentation says that you may encounter some limitations you cannnot do compared to datatemplates in Xaml. But if it is simple it must be doable. There used to be a codeproject article by Sacha Barber you could use as a guidance (if needed).
You pack the creation of the UserControl in a method called private UserControl createMyUserControl(){}
And do something like this:
ItemsControl itemsControl = new ItemsControl();
foreach (var customer in Customers)
{
var usercontrol = createMyUserControl(...);
usercontrol.DataContext = customer;
itemsControl.Items.Add(usercontrol);
}
Second option is less elegant in my opinion, so I would check out the option 1 first.
WPF: How to create Styles in code/and magical Content (see section at the end for extensive sample of a DataTemplate in Code behind)
I think you can replace UserControl with the ContentControl.
Just set the content of the ContentControl to the desired template and use it as ItemTemplate for the ItemsControl.

Changing content of Window (WPF)

I've created a simple WPF application which has two Windows. The user fills in some information on the first Window and then clicks Ok which will take them to the second Window. This is working fine but I'm trying to incorporate both Windows into a single Window so just the content changes.
I managed to find this Resource management when changing window content which seems like it is what I'm after. However, I've search for ContentPresenter but couldn't find much help for how I need to use it. For example, if I use a ContentPresenter, where do I put the existing XAML elements that are in the two Windows? I'm guessing the first Window will go into the ContentPresenter but the second one will need to be put somewhere for when it needs to be switched in.
Any help would be great. A simple working example would be even better.
TIA
A ContentPresenter is normally used when restyling existing controls. It is the place where the Content of a control is placed. Instead you should use a ContentControl, which is simply a control that has a content element. Alternatively, you could directly set the Content of your window.
You extract the contents of your two existing windows into two UserControls. Then you create a new Window which will host the contents. Depending on your business logic, you set the content of that window (or that window's ContentControl if you want additional "master" content) to either of those two UserControls.
EDIT:
As a starting point. This is not complete working code, just to get you started. Note that this is bad architecture; you should probably use a MVVM or similar approach once you get this running!
<Window>
<ContentControl Name="ContentHolder" />
</Window>
<UserControl x:Class="MyFirstUserControl" /> <!-- Originally the first window -->
<UserControl x:Class="MySecondUserControl" /> <!-- Originally the second window -->
In code behind of Window:
// Somewhere, ex. in constructor
this.ContentHolder.Content = new MyFirstUserControl;
// Somewhere else, ex. in reaction to user interaction
this.ContentHolder.Content = new MySecondUserControl;
I use ContentPresenter for snapping in content. In the window, I put something like this:
<ContentPresenter Content="{Binding MainContent}" />
In the view model, I have a property called MainContent of type object:
public object MainContent { get { return (object)GetValue(MainContentProperty); } set { SetValue(MainContentProperty, value); } }
public static readonly DependencyProperty MainContentProperty = DependencyProperty.Register("MainContent", typeof(object), typeof(SomeViewModel), new FrameworkPropertyMetadata(null));
Whatever you set MainContent to will show up in the window.
To keep the separation between view and view model, I typically set the MainContent property to another view model and use a data template to map that view model to a view:
<DataTemplate DataType="{x:Type viewmodels:PlanViewModel}">
<views:PlanView />
</DataTemplate>
I put that data template in some central resource dictionary along with a bunch of other view-model-to-view mappers.

Categories