"FindName" doesn't work if an element added in code - c#

In a WPF app, if a ContentControl is declared in XAML,
<Grid Name="MyGrid">
<ContentControl Name="MyContentControl" />
</Grid>
then I can easily reference it in code using FindName:
ContentControl cc = FindName("MyContentControl") as ContentControl;
cc.Content = ...
But if I add the ContentControl in code instead:
ContentControl contentcntr = new ContentControl();
contentcntr.Name = "MyContentControl";
this.MyGrid.Children.Add(contentcntr);
The FindName doesn't find it.
What's wrong with it in the second case? What's the difference?

The XAML parser automatically registers the names in a namescope, if you create elements like this you may need to do that yourself using RegisterName. (There is an accessor on FrameworkElement as well.)

Related

Create an element from template programmatically

I'd like to move an element from one grid into another and have a problem to assign programmatically a template to the new instance. Further, details of my attempt.
For this purpose, I create an instance of the class together with its visual appearance from the template.
Inside the Window tag I declare the namespace:
xlmns:my="clr-namespace:myNameSpace"
I have a template in resources:
<ControlTemplate x:Key="templateX">
<StackPanel>
<Image Source="pic.png" Width="50" Height="50"/>
</StackPanel>
</ControlTemplate>
and place the element into the grid.
<Grid Grid.Row="2">
<StackPanel>
<my:someClass Template="{StaticResource templateX}" MouseMove="_event">
</StackPanel>
</Grid>
Now, I drag the element, the event "_event" fires. If I push a standard element (e.g. Rectangle) through this, I do the following at the end of the drag-n-drop chain of events:
Rectangle new_instance = new Rectangle();
// place for rectangle's form and color
NewPlace.Children.Add(new_instance);
// place for positioning the rectangle in NewPlace canvas
How, can I do the last part with the element of someClass? If I do
someClass new_instance = new someClass();
NewPlace.Children.Add(new_instance);
the template "templateX" isn't assigned to it.
The issue in this case seems to be that you want to combine two things:
an instance of your custom class (new_instance)
a control template available as a XAML resource
You already know how to create the instance of your class and how to add it to the Children list.
How to retrieve the control template (or for that matter, any other object) from a XAML resource has been discussed in other SO questions, e.g.:
How can I access ResourceDictionary in wpf from C# code?
Accessing a resource via codebehind in WPF
This leads to:
ControlTemplate template = (ControlTemplate)this.FindResource("templateX");
Now, the crucial point is that you do not want to add the control template itself to the Children list. The control template is just a set of instructions how to create a UI tree for your control and bind its properties to those of your control, where appropriate.
Instead, you want to configure new_instance to use the control template you retrieved from the resource. You can do that by assigning the control template to the Template property of new_instance:
new_instance.Template = template;
Once new_instance is added to Children, it will be displayed and it will use your custom control template.

Bind to XAML in code

If I have a resource loader that creates XAML on the fly (in this case a StackPanel is returned) how can I bind to this in the xaml?
My property is:
public StackPanel InfoPane {get;set;}
I have tried this but it doesnt work
<Grid>
<ContentPresenter Content="{Binding InfoPane}" />
</Grid>
(Sorry, too long for a comment.)
You approach is correct, the following minimal example works for me:
XAML:
<Grid>
<ContentPresenter Content="{Binding InfoPane}" />
</Grid>
Codebehind:
public StackPanel InfoPane { get; set; }
public MainWindow()
{
InitializeComponent();
InfoPane = new StackPanel();
InfoPane.Children.Add(new TextBlock { Text = "dynamically created" });
this.DataContext = this;
}
So, your problem must lie somewhere else. Maybe you set your InfoPane after setting the DataContext property of your window? Note that you do not invoke PropertyChanged, so the UI has no way of knowing that InfoPane has changed.
You do not need binding, all you need to do is add the StackPanel to the Children collection when the StackPanel has been created.
That said, my advise is to take a different route:
Put a content control in the Grid and bind its Content to actual content (not a control) and register for each type of content a ContentTemplate so the ContentControl will select the correct template depending on the content being displayed.
Creating UI in code should be avoided as much as possible because it is hard to maintain and WPF has ways to handle content dependent UI.

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.

Letting layout controls appear in a UserControl

I am trying to write a UserControl that will allow both single controls in it (like Label), as well as "layout" controls like StackPanel and friends.
I am having trouble doing that. The code I have works for single controls, but not for layout controls. I have a feeling this is an obvious fix, I am new to WPF. Here is the UserControl XAML:
<UserControl <!-- namespaces omitted for brevity -->>
<UserControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{TemplateBinding Content}" />
</DataTemplate>
</UserControl.ContentTemplate>
</UserControl>
When I try to use it like this:
<my:SpecialUserControl>
hello
</my:SpecialUserControl>
It's fine. But when I try to do something like
<my:SpecialUserControl>
<StackPanel>
<!-- stuff -->
</StackPanel>
</my:SpecialUserControl>
I get an error in Visual Studio Intellisense saying
The specified value cannot be assigned to the collection. The following type was expected: UIElement
And when I run the app (it builds), I get this exception at that place in the XAML:
'Add value to collection of type System.Windows.Controls.UIElementCollection threw an exception.' Line number x and line position y.
What can I do to make my UserControl able to accept any type of content?
The ContentPresenter should be a part of the ControlTemplate (<UserControl.Template>) of your control, not inside your ContentTemplate. I think that could be your problem.
Inherit your UserControl from ContentControl. ContentControl already has Content property combined with ContentPresenter inside ControlTemplate.
You can also use microsoft blend to get ContentControls visual tree.

How to create DataTemplate at runtime?

I have defined the DataTemplate in the XAML, however I need to change it to be defined at run time, because its binding is dynamic.
<UserControl.Resources>
<DataTemplate x:Key="myCellTemplate">
<TextBlock Text="{Binding Description}" Margin="4"/>
</DataTemplate>
</UserControl.Resources>
Is there any way to define it in code-behind?
Thank you.
You might be able to accomplish what you need with a custom DataTemplateSelector and I'd recommend that approach, if possible. That said, it is possible to create a DataTemplate in code:
Type type = typeof(MyUserControl); //for example
var template = new DataTemplate();
template.VisualTree = new FrameworkElementFactory(type);
return template;
In this context, type is the type of visual element you want to have as the root of your template. If necessary, you could add additional elements using the factory, but in the one case where I've used this, I simply created UserControl elements to represent the different templates I was dynamically creating.
My apologies, this apparently isn't supported in Silverlight. In Silverlight, you have to use XamlReader.

Categories