17
votes

I'm trying to localise a WinForms app for multiple languages. I'm trying to find a way to set my form labels/buttons text properties to read from the resources file in the designer (rather than having to maintain a chunk of code that sets them programatically).

I've found I can set form.Localizable=true, but then the resources are read from a file alongside the form, but many of mine are shared across multiple forms.

Is there any way to set a label's text in the designer, to a value stored in a project-level resx file?

5
What's the need behind doing this in the designer? The effort of doing this in code minimal.Walter
Have you had any luck with this? I need this as well. I want all my forms to bind to a single resource file.user93202
Sadly not. I'm wiring a lot of stuff up in code :(Danny Tuppeny

5 Answers

4
votes

To answer the question, no.

But IMO, this should not be done anyways if the text will be static.

Have a read at my answers on localization and resources:
Resource string location
Globalize an existing Windows Forms application
Using .resx files for global application messages

4
votes

I think I found a way to do this!

First in your Resources.resx set the Access Modifier to Public.

After that in the designer generated code (Form.Designer.cs) you can write this to the appropriate control:

this.<control>.Text = Properties.Resources.<stringname>

for example:

this.footerLabel.Text = Properties.Resources.footerString;

ps.:I don't know how ethical this solution is, but it works!

4
votes

Easy enough to implement, by the way, this can be done for any type of control you like to bind to a resource, or any other class. I do this for static classes like my application settings as well.

Entering code like this:

textBox2.DataBindings.Add("Text", source, "<className>.<PropertyName>");  

is not giving me a "good feeling", never mind the spelling

Here is a litle sample of the above label that provides a dropdown on the resources of a application.

First the control, contains 1 new property named ResourceName the magic comes from the editor, this one is specified in the annotation above the property and is called ResourceDropDownListPropertyEditor

[Editor(typeof(ResourceDropDownListPropertyEditor), typeof(System.Drawing.Design.UITypeEditor))]

The code for the label class:

/// <summary>
/// Label bound to resource
/// </summary>
/// <remarks>
/// The bitmap does not appear in the Toolbox for autogenerated controls and components.
/// https://docs.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-provide-a-toolbox-bitmap-for-a-control</remarks>
/// <seealso cref="System.Windows.Forms.Label" />
[ToolboxBitmap(typeof(Label))]
public partial class ResourceLabel : Label
{

    /// <summary>
    /// backing field for the resource key property
    /// </summary>
    private string mResourceName;
    [Browsable(true)]
    [DefaultValue("")]
    [SettingsBindable(true)]
    [Editor(typeof(ResourceDropDownListPropertyEditor), typeof(System.Drawing.Design.UITypeEditor))]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Description("Select the resource key that you would like to bind the text to.")]
    public string ResourceName
    {
        get { return mResourceName; }
        set
        {
            mResourceName = value;
            if (!string.IsNullOrEmpty(mResourceName))
            {   
                base.Text = Properties.Resources.ResourceManager.GetString(mResourceName);
            }
        }
    }

    /// <summary>
    /// Designer helper method: https://msdn.microsoft.com/en-us/library/ms973818.aspx
    /// </summary>
    /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
    private bool ShouldSerializeResourceName()
    {
        return !string.IsNullOrEmpty(ResourceName);
    }    
    /// <summary>
    /// Will be default text if no resource is available
    /// </summary>
    [Description("default text if no resource is assigned or key is available in the runtime language")]
    public override string Text
    {
        get { return base.Text; }
        set
        {
            // Set is done by resource name.
        }
    }
}

Here is the class used for the drop down:

/// <summary>
/// used for editor definition on those properties that should be able 
/// to select a resource
/// </summary>
/// <seealso cref="System.Drawing.Design.UITypeEditor" />
class ResourceDropDownListPropertyEditor : UITypeEditor
{
    IWindowsFormsEditorService _service;

    /// <summary>
    /// Gets the editing style of the <see cref="EditValue"/> method.
    /// </summary>
    /// <param name="context">An ITypeDescriptorContext that can be used to gain additional context information.</param>
    /// <returns>Returns the DropDown style, since this editor uses a drop down list.</returns>
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
    {
        // We're using a drop down style UITypeEditor.
        return UITypeEditorEditStyle.DropDown;
    }

    /// <summary>
    /// Displays a list of available values for the specified component than sets the value.
    /// </summary>
    /// <param name="context">An ITypeDescriptorContext that can be used to gain additional context information.</param>
    /// <param name="provider">A service provider object through which editing services may be obtained.</param>
    /// <param name="value">An instance of the value being edited.</param>
    /// <returns>The new value of the object. If the value of the object hasn't changed, this method should return the same object it was passed.</returns>
    public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
    {
        if (provider != null)
        {
            // This service is in charge of popping our ListBox.
            _service = ((IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)));

            if (_service != null)
            {


                var items = typeof(Properties.Resources).GetProperties()
                            .Where(p => p.PropertyType == typeof(string))
                            .Select(s => s.Name)
                            .OrderBy(o => o);

                var list = new ListBox();
                list.Click += ListBox_Click;

                foreach (string item in items)
                {
                    list.Items.Add(item);
                }
                if (value != null)
                {
                    list.SelectedValue = value;
                }

                // Drop the list control.
                _service.DropDownControl(list);

                if (list.SelectedItem != null && list.SelectedIndices.Count == 1)
                {
                    list.SelectedItem = list.SelectedItem.ToString();
                    value = list.SelectedItem.ToString();
                }

                list.Click -= ListBox_Click;
            }
        }

        return value;
    }

    private void ListBox_Click(object sender, System.EventArgs e)
    {
        if (_service != null)
            _service.CloseDropDown();


    }
}

In the end what you get will look like this at design-time: The design-time View

The resource names are created when you drop the control on your form, changes are not seen till you re-compile and close/open the form or drop a new label on the form.

0
votes

The only way I can think of would be to create a custom control that would add a property for the resource name. When the property is set, grab the value from the project resource file and set the text property with it. You will want to make sure that Text doesn't get serialized or it might overwrite the value set by ResourceName.

public class ResourceLabel
    : Label
{
    private string mResourceName;
    public string ResourceName
    {
        get { return mResourceName; }
        set
        {
            mResourceName = value;
            if (!string.IsNullOrEmpty(mResourceName))
                base.Text = Properties.Resources.ResourceManager.GetString(mResourceName);
        }
    }

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public override string Text
    {
        get { return base.Text; }
        set 
        { 
            // Set is done by resource name.
        }
    }
}
0
votes

I have just been looking at this very thing.
If you own the control, ie it is your own custom control, you can use CodeDOM

Read this article for some background and download this example to see how it's done.

In our app we need to replace placeholders with "DisplayText" form the database.
So we have Text properties like "Order {Product}" and we want to replace with GetDisplayText("Order {Product}")`.

So in order to do this I have added the following code:

                statements.OfType<CodeAssignStatement>()
                    .Where(s => s.Left is CodePropertyReferenceExpression && ((CodePropertyReferenceExpression)s.Left).PropertyName == "Text")
                    .ToList().ForEach(s =>
                    {
                        s.Right = new CodeMethodInvokeExpression(
                            new CodeMethodReferenceExpression(new CodeTypeReferenceExpression("Core.DisplayText"), "GetDisplayText"),
                            s.Right);
                    });

However I am still experimenting with it and I haven't created a working solution yet... But it may help you.

:-)