11

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?

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Arsen Mkrtchyan
  • 49,896
  • 32
  • 148
  • 184
  • 1
    Have you tried the [FrameworkElement.Name](http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.name.aspx) property? – DmitryG Apr 03 '12 at 15:11
  • Yea Dmitry, but Name can be empty, I don't want to put Name on every control exclusively for logging purposes – Arsen Mkrtchyan Apr 03 '12 at 15:14
  • @ArsenMkrt, the [PersistId](http://msdn.microsoft.com/en-us/library/system.windows.uielement.persistid.aspx) property seems to be what you're looking for. Alas, it is now obsolete and has apparently no replacement. Maybe you can fall back to generating unique identifiers yourself, in that case see [this question](http://stackoverflow.com/q/750947/464709). – Frédéric Hamidi Apr 03 '12 at 15:18
  • @FrédéricHamidi, sure generating id myself is a good idea, but in post you mentoined the identifier will be changed after application re-start...which will make log unusable, and I don't think there is any way to generate stable identifier – Arsen Mkrtchyan Apr 03 '12 at 15:25
  • My understanding is way XAML builds a page it does not require a unique identifier for a UI element. In absence of assigning a unique identifier I don't think you are going to find one. GetHashCode might work. – paparazzo Apr 03 '12 at 15:30

7 Answers7

10

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

Community
  • 1
  • 1
Jason Ridge
  • 1,868
  • 15
  • 27
  • I tested and the problem is the GetHashCode() is only for that session. If you shut the app down and restart the UI elements will all get new HashCodes. But still +1. – paparazzo Apr 03 '12 at 15:36
  • @ExitMusic, GetHashCode will be changed at least after application re-start, which will make log unusable...I want to have more stable identifier – Arsen Mkrtchyan Apr 03 '12 at 15:36
2

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.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Ricky
  • 21
  • 1
  • Why I need an attribute? I can set all Name properties of my UI elements, I just want to find a way without setting anything, because it is possible in winform – Arsen Mkrtchyan Apr 20 '12 at 20:47
2

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.

Arsen Mkrtchyan
  • 49,896
  • 32
  • 148
  • 184
0

see FrameworkElement.Tag Property

The Tag property can be used. It is of type object and can be set to anything.

ergohack
  • 1,268
  • 15
  • 27
0

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}");
    }

nkanani
  • 548
  • 1
  • 6
  • 7
0

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.

myermian
  • 31,823
  • 24
  • 123
  • 215
  • thanks for answer @m-y, but Name is not what I am looking for, at first I don't want to give name all UI controls excursively for logging, and second it may be impossible for a grid with a lot of edit controls in cell templates... – Arsen Mkrtchyan Apr 03 '12 at 15:21
0

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.

DmitryG
  • 17,677
  • 1
  • 30
  • 53
  • Hmmmm... seems AutomationId should be set too,and if it is not set, it returns Name which is empty in our case... I guess no way to do what I am looking for... – Arsen Mkrtchyan Apr 03 '12 at 15:48