We are using a third party editing control (TxTextControl) that has various toolbars and other controls which can be attached to it. This is done by placing the toolbars somewhere in the view and associating them by name. It looks something like this:
<tx:RulerBar x:Name="rulerBar"/>
<tx:TextControl RulerBar="rulerBar"/>
This works fine when defined in XAML, but we have a scenario where we need to build this procedurally. For some reason when we try associate the RulerBar (or any other toolbars/controls) it throws an error that it can't locate the toolbar. For example:
DockPanel dock = new DockPanel();
dock.Children.Add(new RulerBar { Name="rulerBar" });
dock.Children.Add(new TextControl { RulerBar = "rulerBar" });
I have also tried adding Loaded event handlers and deferred the RulerBar association until both controls were fully loaded but I still get the same error. Should this simply work, or is there some trick I'm missing? Unfortunately name association is the only mechanism they provide, and we can't associate the controls by reference.
In WPF, the XAML parsing process associates the value of an element's x:Name attribute with its name, enabling lookup by name.
So why does assigning the Name property in XAML work? This is since the Name property is marked with an attribute which instructs the parser to treat it like it treats an x:Name attribute.
Since you do not implement the same logic as the xaml parser does, you will have to do some additional coding in order for your code to work..
I myself did not know how to do that, but fortunately #Grx70 did.. see his answer.
#Eyal Perry is correct with his diagnose of the problem. In order to make it work you should use the FrameworkElement.RegisterName method. Here's an example of how to use it:
DockPanel dock = new DockPanel();
var ruler = new RulerBar { Name = "rulerBar" };
dock.RegisterName(ruler.Name, ruler);
dock.Children.Add(ruler);
dock.Children.Add(new TextControl { RulerBar = "rulerBar" });
Related
I am assigning a custom class with some information I need for each TabPage created:
MyClass custom = new MyClass();
TabPage tabPage = new TabPage();
tabPage.Text = ...;
tabPage.Tag = custom;
tabPage.Controls.Add(...);
Unfortunately, the moment I click between tabs, or have a context menu and click something, the Tag object is lost.
From my understanding, you can set and use whatever you want inside Tag objects, so what's the issue?
I need this to be persistent until I want it to.
The issue still persists without explanation. I even tried this post, with the same result.
Apparently, I was using a third party library that has setting "HotTrack" information as boolean (true/false) using the Tag property of the TabPages.
Removing this in the library (as I do not need it) allowed me to store my own information without getting lost or overriden.
With WinForms programs I've become accustomed to marking the Modifiers property of a control as 'Private' to prevent external classes and whatever else have you from being able to see and mess with them.
Being still very green with WPF, I see no obvious equivalent in WPF that allows me to make it so external classes cannot see a control I drop onto a form or another user control or what not. I did notice something of x:FieldModifier = "Private" but I get the error "x:FieldModifier = "Private" is not valid for the language C#".
How do I mark a control as Private so it cannot be viewed or accessed by external class objects?
TL;DR
Most of the time you don't need to worry about this in WPF. However:
If you name a XAML element using the x:Name attribute, then you can use the x:FieldModifier attribute to control the visibility of the auto-generated field representing that element. This attribute value is language- and case-specific.
If you don't name a XAML element, then don't bother using the x:FieldModifier attribute.
Read on for a more detailed explanation.
Explicit naming and generated fields
If you create a new WPF application project in Visual Studio, it will create a MainWindow class, the XAML for which looks something like this:
<Window x:Class="StackOverflow.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>
</Grid>
</Window>
If you look at the code-behind class for this window, it will look like this:
// Several using statements...
namespace StackOverflow
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
Note the use of the partial keyword to denote this as a partial class. If you navigate to the project's obj\Debug folder using Windows Explorer, you will find a file called MainWindow.g.cs: it is this file that contains the code generated by the IDE from your XAML (it is basically the equivalent of the *.Designer.cs file from WinForms).
Your window has a Grid on it, but note that it is not surfaced directly anywhere in the code for MainWindow. Now edit your XAML to give the Grid a name:
<Grid x:Name="_myGrid">
Compile the application, and open the MainWindow.g.cs file again. You will see that the following line has been added:
internal System.Windows.Controls.Grid _myGrid;
Setting the x:Name property of the element in the XAML has caused the code generator to add a field with that name. The field is marked as internal which means it is accessible to all types in your project, but not to any other projects that reference your project.
So basically, if you do not explicitly name an element in the XAML using the x:Name attribute, the code generator will not create a named field for the element in the code-behind class, and your element will effectively be private (this means that the class itself cannot access the element directly either).
Nameless UI elements can still be accessed from code (if you have an instance)
An element without a name can still be accessed via code, by "walking" the visual tree of a Window instance. For example, because the window's content is set to a single Grid element, you can access that grid through code like so:
Grid grid = (Grid) this.Content;
this here refers to the MainWindow class instance.
WinForms has exactly the same "problem" as WPF in this regard: even controls that are not explicitly named can still be accessed through code. Imagine a WinForms Form with a single Button control on it. You can access that button like so:
Button button = (Button) this.Controls[0];
The fact that the button had a default Modifiers value of "Private" did not stop the code from being able to access it.
The FieldModifier attribute controls generated field visibility
Coming back to WPF, particularly if you're using the Model-View-ViewModel (MVVM) pattern, you will rarely need to explicitly name your elements in the XAML, hence the default behaviour will be fine. However, if you do find that you need to name your XAML elements, and you wish to "hide" these elements, then you can use the x:FieldModifier attribute to set the visibility of an element to private instead of the default internal. The value used for the attribute is language-dependent and case-sensitive, eg. for C#:
<Grid x:Name="_myGrid" x:FieldModifier="private">
I want to create a custom MessageDialog which in terms of its interface is exactly the same as the provided MessageDialog except for one thing; The Content property acts like that of a ContentControl so I can place custom UI elements in it.
Though I cannot for the life of me figure out how the developers were able to create a class that could be 'shown' in the UI without specifying that it inherits from any sort of control in the framework.
How is it possible to create a regular class and specify its 'Template' in code?
Thank you
You can accomplish this by creating a Popup, set it's Child. and then open it. You can create any XAML controls in code, so this is not an issue.
Popup popup = new Popup { Child = CreateXamlControlInCode() };
popup.IsOpen = true;
For step by step instructions, see this blog post.
For logging user actions in my WPF forms, I added some global event handlers
I want to log exactly which control fire the event, is there some unique identifier for a wpf UIElement like ClientId in ASP.Net?
Why don't you use the Hash Code.
You can compare the values to make sure they are the same object, and its easy to get them with .GetHashCode()
Edit
Obviously this is different every time you run the program, so actually this is prolly a bad idea, unless you want to update the log each time the process is logged. Still possible though
I mean you could store a hash value for each object at the time the log is created, but i don't know if I like that
Seems I found answer to my question, the answer is No, now way to do that, As noted in MSDN here (http://msdn.microsoft.com/en-us/magazine/dd483216.aspx)
Notice that the top-level Window control definition does not contain a
Name attribute. This is significant because, as we'll see shortly,
when you write test automation, an easy way to get a reference to a
control using the MUIA library is to access the AutomationId property,
which is generated by the compiler from the control's Name attribute.
Controls without a XAML Name attribute will not receive an
AutomationId property. This idea is a specific, low-level example of
the importance of considering application design issues for things
such as security, extensibility, and test automation.
One way you can do this is with a custom attribute. Like so...
The UIElement you want to log (UserControl for example):
[UserInterfaceID(ID = "{F436E9B3-C2F6-4CF8-8C75-0A2A756F1C74}")]
public partial class MyUserControl : UserControl
{
InitializeComponent();
// or whatever...
}
Then you need the custom attribute class
[System.AttributeUsage(AttributeTargets.Class)]
public class UserInterfaceIDAttribute : Attribute
{
public Guid ID { get; set; }
}
Now in your code, you can do something like this:
MyUserControl control = new MyUserControl();
foreach(object att in control.GetCustomAttributes(typeof(UserInterfaceAttribute),false))
{
UserInterfaceAttribute uiAtt = (UserInterfaceAttribute)att;
Guid theID = uiAtt.ID;
}
Because you are tagging the control with an attribute in the code, the unique identifier never changes no matter how many times you kill / launch the application.
Of course this is a basic example that shows how to access the ID but you will probably want to use some type of Aspect Oriented Programming. I do exactly this kind of thing using Castle Windsor Interceptors, but that is out of the scope of this post.
Ideally, you will be accessing this ID when there is some kind of event that gets fired. Using interceptors allows you go capture method calls before they are invoked wherein you can look up the ID as shown above and log the action. Alternatively, you can just use
this.GetCustomAttributes(...)
in some method when an event is fired on the control and embed your Logging code there. This pattern is not the best because you're sprinkling cross-cutting concerns all over making some type of Aspect Oriented Programming approach better...but again I digress and it is out of the scope of this post...but you get the idea.
Hope this helps.
You're just looking at adding a Name or x:Name so that the Window/UserControl/Page exposes the control to the rest of the class with the specified name.
<Window ...>
<Grid>
...
<!-- These controls are named, so you can access them directly by name -->
<Button x:Name="btnMyNamedButton" ... />
<Button Name="btnMyOtherNamedButton" ... />
<!-- This control is not named, so you can not directly access it by name -->
<Button ... />
<Grid>
</Window>
public partial class MyWindow : Window
{
public MyWindow()
{
InitializeComponent();
//btnMyNamedButton can be accessed
//btnMyOtherNamedbutton can also be accessed
//The third button can be accessed, but not directly by name.
}
}
Also, you can always use the FrameworkElement.Tag object. It's meant to store arbitrary information, so you can use this as a unique identifier if you want to.
I believe, for logging user actions, you can use the UIAutomation tree and the AutomationElement.AutomationId property, because this approach is supported in all standard UI controls by default. Many third-party controls are also supports the AutomationId for their elements (e.g. grid cells). An AutomationId is useful for creating test automation scripts.
see FrameworkElement.Tag Property
The Tag property can be used. It is of type object and can be set to anything.
I think UIElement.Uid should work. Assign it in XAML and access it in code.
<Label Uid="FirstName"/>
OR in a style property
<Setter Property="Uid" Value="SecondName"/>
Then refer in C# code:
if (Keyboard.FocusedElement is UIElement uiElement)
{
Debug.WriteLine(this, $"{uiElement.Uid}");
}
FindName is broken for me :(
The object I am looking for is there. I have proof.
Here is the scenario:
ToggleButton button = (ToggleButton)sender;
Popup popup = (Popup)button.FindName("popSelectIteration");
popup is null but not always. Just sometimes. But even when it is set to null the child I am looking for is there.
I put a break point in when it was null and grabbed these two screenshots.
The is where FindName is returning null for "popSelectIteration".
But if you dig into the watch, you see that the child is there.
So what am I missing? Why does FindName not find it? As you can see from the screen shot this is not a timing issue (the FindName watch is null but the direct path is fine).
Is there a better way to find a control?
Side Note: If you are intersted in the XAML for the toggle button in question it can be found in this question: WPF - FrameworkElement - Enumerate all decendents?.
Update: I did some digging to see why this fails some times and other times it works. I have an animation that calls NameScope.SetNameScope((DependencyObject)form, new NameScope()); (Full method code here). Right after that call the FindName starts to fail.
I don't really understand that call. I think I copied and pasted the code. Anyway, I commented it out. But I would love know why this is failing.
I would guess it has to do with the difference between the visual and logical tree. The control is in the logical tree but maybe the template for this control has not been applied yet and therefore FindName won't return anything useful.
You could try to call ApplyTemplate(); on the container first.
This would also explain why it returns something sometimes.
Try
LogicalTreeHelper.FindLogicalNode(button, "popSelectIteration");
Little late to the party (and not actually answer to OP question), but
when you add elements dynamically, they are not findable by FindName.
You need to register them by calling RegisterName.
Example:
string number = GenerateNumber();
Button myButton = new Button();
myButton.Content = number;
myButton.Name = "button_" + number;
RegisterName(myButton.Name, myButton);
Panel.Children.Add(myButton);
object o = Panel.FindName(myButton.Name);
Maybe someone might find this useful.
In my experience, this happens when you add items via code-behind. I've found that you can fool FindName() (or the animation framework) via name scopes. That is, when you create your control, you do
NameScope.GetNameScope(yourContainer).RegisterName("name of your control", yourControlInstance);
For this to work reliably, though, you must make sure that you unregister the name:
NameScope.GetNameScope(yourContainer).UnregisterName("name of your control");
Posting this for future reference.
I have meet the same question now, but I use the method like so:
#region Override - OnApplyTemplate
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.PART_ListViewLeft = GetTemplateChild(cPART_ListViewLeft) as ListView;
this.PART_ListViewCenter = GetTemplateChild(cPART_ListViewCenter) as ListView;
this.PART_ListViewRight = GetTemplateChild(cPART_ListViewRight) as ListView;
this.PART_GridViewLeft = GetTemplateChild(cPART_GridViewLeft) as DsxGridView;
this.PART_GridViewCenter = GetTemplateChild(cPART_GridViewCenter) as DsxGridView;
this.PART_GridViewRight = GetTemplateChild(cPART_GridViewRight) as DsxGridView;
if(this.PART_ListViewLeft!=null)
this.PART_ListViewLeft .AlternationCount = this.AlternatingRowBrushes.Count;
if(this.PART_ListViewCenter!=null)
this.PART_ListViewCenter .AlternationCount = this.AlternatingRowBrushes.Count;
if(this.PART_ListViewRight!=null)
this.PART_ListViewRight .AlternationCount = this.AlternatingRowBrushes.Count;
// ApplyTempleted = true;
CreateColumnLayout();
}
#endregion
If the Control is dynamic create and of which or whose container the 'Visibility' is set to hide or Collapsed, then the code this.PART_ListViewLeft = GetTemplateChild(cPART_ListViewLeft) as ListView; will always return null, the reason is that the datatemplete has not yet been applied before OnApplyTemplate being called.
I would suggest to avoid using FindName function, based on my experience, expecially problematic when you try to find something in the DataTemplate applied to some control.
Instead , if it possible (based on your software architecture) declare Popup in XAML and
refer to it like resource or use Binding to set some Model property to it's reference.
Good luck.
Try to use button.FindResource("popSelectIteration")
ellipseStoryboard.Children.Add(myRectAnimation);
containerCanvas.Children.Add(myPath);
After you add register the controls like
RegisterName("TextBlock1", Var_TextBox);
or
RegisterName(myRectAnimation.Name,myRectAnimation);
RegisterName(myPath.Name,myPath);