Inherit properties to a WPF Page element - c#

This may be a very simple question, so my apologies.
My problem here is that I want to build a Multilingual WPF App using C#, but I don't know how to make my different Page elements inherit the same method which makes my MainWindow translate to different languages. The app is done, I'm just translating it to English (My native language is Spanish).
I'm using Resource files to translate it.
Code for the language translation:
private void Languages_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//This is the combobox in which you select the language to display the app in
Set_Language();
}
//This is the method to invoke the Resource Manager and all the stuff from the resource file.
private void Set_Language()
{
if (!boolInit)
{
strLanguage = "LeitProjekteV2._0.Languages." + ((ComboBoxItem)LanguageSel.SelectedItem).Name.ToString();
ResourceManager LocRm = new ResourceManager(strLanguage, typeof(MainWindow).Assembly);
//Menu buttons
lblMenu.Content = LocRm.GetString("strMainMenu"); //The names inside the "" are the names of the resource in the Resource file which, depending on the language selected(Spanish, English and German)
//Change the text of whatever I choose; in this case, a Label named 'lblMenu'
MapButt.Content = LocRm.GetString("strMapButt");
BuscButt.Content = LocRm.GetString("strBusButt");
AgeButt.Content = LocRm.GetString("strAgeButt");
ComButt.Content = LocRm.GetString("strComButt");
InfButt.Content = LocRm.GetString("strInfButt");
LoginButt.Header = LocRm.GetString("strLoginButt");
RegisterButt.Header = LocRm.GetString("strRegisterButt");
ContacButt.Header = LocRm.GetString("strContacButt");
MasButt.Header = LocRm.GetString("strMoreButt");
//Here go the names of everything the Pages contain that I want to translate, just like above
//Have no idea how to inherit this method to all the pages
}
}
Now, I have several pages embedded in the same MainWindow.xaml, so that you click the button "Map", a Frame changes it's content to a Page named Map.xaml, and so on for other buttons.
But how do I make those Pages also translate?
Since the Set_Language() method takes the string value of the Combobox in order to select the correct Resource File, I don't want to create one combobox for every Page that I have, albeit that would eliminate my problem.
Any help? Sorry for the horrible way of asking, I'm still getting the hint here.
Thanks.

Use the below:
var wnd = Window.GetWindow(this); //get current window
Cast it to your window class and expose your language as a public property
Use your page to get the property by finding the current window
You may create a parent page class that do the above, and inheriting it for all your pages so you dont repeat code

I can see the difficulty you are facing is that you can't find a way to share the combobox across the main window and the pages hosted in the frame.
You can set a global variable that is accessible from the whole application, a good place is in application settings. Then when you make a selection with the Combobox, you just update the selected value to that variable.
Then call each page's Set_Language() method when they are being loaded into the Frame. In the Set_Language() method of each page, you can query what is been set to the variable stored in the application settings.
If you want to quick solution, create a static class to hold the selected language is also OK.
static class UserSelections
{
public static string Language { get; set; }
}

Related

Change "text" property of all buttons on form incrementally

I have created a soundboard in Visual C# where I click buttons and it plays an MP3 file. I currently have buttons named SBut_01 to SBut_x appropriately.
I store paths to the MP3 files that are being associated to these buttons in an xml file, which is fed in on startup and all "captions" for the corresponding buttons (which are extracted from the path for each key) are stored in an array named ButtonCapts[x].
An example of something that could be in ButtonCapts[1] would be Foo stored as a string.
Originally when I created this soundboard, I manually specified each button's caption using a method I named AssignButtonCaptions. Which worked crudely like so:
public void AssignButtonCaptions{
SBut_01.Text = ButtonCapts[1];
SBut_02.Text = ButtonCapts[2];
...
}
However, as this soundboard expands in scope this is becoming a very large section of hardcode, so I would like to automate this as much as possible.
I've thought that I could probably use a foreach loop in the instance of ButtonCapts[], but I don't at present know a way to loop through all of my SBut_xx buttons. Is it possible to detect all buttons on a form that are prefixed by SBut_ and iterate through them from 1 to x?
It would be great if something like below could be accomplished. Please note that I have assumed that each button has been put in an array named SButList as an example of what I'm trying to accomplish. I understand there is no way this code would work in its current form and am not even sure if objects such as buttons could be stored in such a way:
//Some kind of method to put all buttons prefixed with SBut_ in to an array named ButtonList here
//Then, iterate through all of the buttons and assign the captions
int i = 1;
foreach (button SBut in ButtonList)
{
SBut.Text = ButtonCapts[i];
i++;
}
Is this possible?
Thanks
Ok, so I've figured this out with some help from Anu6is. I'll document what I found so that it may help others.
Turns out that you can easily extract all controls in a form by using the following to accomplish this:
var myButtons in Controls.OfType<Buttons>().Where(button => button.Name.Contains("Something"))
The issue I was having was that I needed to iterate through each parent directory, as my buttons were inside of a Group Box, which was in turn inside of one of many Tab Pages within a Tab Control object. I just did a quick foreach loop to iterate through all of these tabs and group boxes in order to resolve this.
My new working code looks like this, which in my opinion is much better:
public void AssignButtonCaptions()
{
foreach (TabPage Page in tabControl1.TabPages)
{
foreach(GroupBox ButtonContainer in Page.Controls.OfType<GroupBox>())
{
foreach (var SButton in ButtonContainer.Controls.OfType<Button>().Where(button => button.Name.Contains("SBut")))
{
int ButtonNum = Int32.Parse(SButton.Name.Split('_')[1]);
SButton.Text = ButtonCapts[ButtonNum];
}
}
}
}

How to refactor duplicated code in Windows Forms?

I'm currently working on refactoring a lot of duplicated code in a couple of UserControls in Windows Forms project.
The architecture leaves much to be desired with a lot of logic implemented inside the UI layer. This will be fixed eventually, I'm now working on improving what I have on my hands now.
The problem is, that a lot of duplicated code relates directly to controls, for instance:
private void InitDestinationPathControls(string path)
{
if (someField)
{
tbOne.Enabled = false;
tbOne.Visible = false;
btnTwo.Enabled = false;
btnTwo.Visible = false;
tbOne.Text = string.Empty;
return;
}
// (...)
}
Don't get too attached to the cited code itself, it is just an example.
I'd like to move this code to a common base class, but it relies directly on specific fields (even though they are exactly the same in all controls too). Those fields, on the other hand, are generated by the designer, so I cannot extract them to the base class.
The only thing that comes to my mind is to pass those fields as parameters to a method in base class, but then if some method uses a lot of them, I'll end up with a monstrous interface part and that won't actually improve the readability too much.
How can I deal with such common parts of user controls in Windows Forms?
Apparently you have a combination of several controls that appears in several forms. In other words: you have for instance some buttons, comboboxes, etc, that you want to show on different forms, and you want them to have the same behaviour.
If not only the behaviour of these controls is the same on all forms, but also the layout, then consider to create a class derived from UserControl.
The UserControl hides from the outside world which controls are shown, how they are visualized and how they behave.
I assume that you already pulled the model out of the visualisation of the data.
If all instances of this user control should all call the same functions of possibly a different object of the same class, then give your special user control a property that represents this class, or at least an interface. During construction you can plug in the actual class that should handle the actions after operator input.
If, on the other hand, the layout differs on each form, but the collection of buttons, comboboxes, etc and their behaviour is similar on all forms that show this collection of controls and they have a lot of common behaviour, consider to create your own ControlCollection.
For instance, if on several forms you have a button to select a (text) file, labels with the name, size and creation date of the selected file, and an edit box that shows the content of the text file, but you want to layout them differently, consider something like this:
class FileDisplayControls : IDisposable
{
public Button ButtonSelectFile {get;} = new Button();
public Label labelFileName {get; } = new Label();
public Label labelFileSize {get; } = new Label();
public TextBox textFileContents {get; } = new FileContents();
private void ButtonSelectFile_Clicked(object sender, ...)
{
// TODO: open file dialog, display result in labels and text box
}
}
Constructor can set initial layout properties of the controls, and subscribe to events, such that the controls will react on user input.
The user of the class (= code, not operator) immediately has a collection of controls that have some standard behaviour, like react on button click. All he has to do is set the location of the items in his own form. If desired change other layout properties (colour, background) and put them on his own form.
If you want to prevent that others change other visual aspects of the controls than the position, don't publish the control themselves, only the position of the control:
public System.Drawing.Point LocationSelectFileButton
{
get => this.buttonSelectFile.Location;
set => this.buttonSelectFile.Location = value;
}
public System.Drawing.Point LocationFileContentTextBox
{
get => this.textBoxFileContent.Location;
set => this.textBoxFileContent.Location = value;
}
etc.
If needed, you can add events for users:
public event EventHandler SelectedFileChanged;
public string FileName => this.labelFileName.Text;
public string FileContents => this.textBoxFileContent.Text;
etc.
Conclusion
The solution that you choose depends on the similarity between the controls on the various forms:
if Behaviour and Layout are all the same: UserControl
If only position and a few properties different: special class with the properties that are different. This way you can force a more similar style: all "Select File" buttons look similar.
If only one or two behaviours are different: add Action<...> properties or events
If you want full control of the layout: expose the Controls.
The behaviour that is common for all you forms that show these controls (in my example: how to select a file and what to do when a file is selected) is inside the class.
repeated code can be extracted to method (possibly in base class, or as static method in helper class)
public void DisableControls(params Control[] controls)
{
foreach(var c in Controls)
{
c.Enabled = false;
c.Visible = false;
if (c is TextBox t)
{
t.Text = string.Empty;
}
}
}
private void InitDestinationPathControls(string path)
{
if (someField)
{
DisableControls(tbOne, btnTwo);
return;
}
// (...)
}

How to create a static instance of a page at application startup time?

I currently have a MainWindow that acts as a frame to navigate to other pages in my solution. The problem is, i require one of my pages to be instantiated for the entire duration of my application instead of every time when i navigate to a page, that page gets re-instantiated. I have tried the KeepAlive='true' property for my page but it did not work.
I would like to know if theres a way to implement "this static instance of a page" method for my codes. Thanks. (p.s im not looking or planning to implement the MVVM approach)
public MainWindow()
{
InitializeComponent();
//Instanciate ApiStartup class and Initialize the HTTPClient
ApiStartup.InitializeClient();
Application.Current.MainWindow = this;
Loaded += OnMainWindowLoaded;
}
private void OnMainWindowLoaded(object sender, RoutedEventArgs e)
{
ChangeView(new DetectionPage());
}
public void ChangeView(Page view)
{
MainFrame.NavigationService.Navigate(view);
}
private void quiz_Click(object sender, MouseButtonEventArgs e)
{
var mainWindow = (MainWindow)Application.Current.MainWindow;
mainWindow?.ChangeView(new DetectionPage());
}
If you want to use static value for use in entire application from start to end then you can mention it in app.config file. And easily you can use like ((App)Application.current).KeepAlive in your any xaml file.
A page has the keepalive property.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.page.keepalive?view=netframework-4.8
You may set that to true on whichever page you like.
When you later use navigation methods to navigate to that url, the first instance of the page will be returned.
If that doesn't suit for whatever reason you could cache instances of pages using a dictionary with key type and value page (instance). Then implement some logic decides which pages you do or do not cache in there.
You said you don't want to use mvvm but I think it best to at least mention what almost everyone else does. For other people who intend working in a team then MVVM will be expected and hence for other people reading:
Since pages and frames come with a journal and memory overheads that are often undesirable, most teams (I have encountered) use usercontrols rather than pages. These are presented via a contentcontrol. You can set content to a usercontrol but most teams use viewmodel first. State that matters is bound and templated from an instance of a viewmodel. To retain state between "navigations" a reference to the viewmodel is retained - which is almost guaranteed to be way lighter on memory than any sort of view.

Controlling a rich text box control that is on form 1 from form 2

I'm sorry if this seems incredibly obvious or a very much commonly asked question, but I've been searching and looking over posts for a while now and i still can't seem to get it.
I'm just getting into learning C# and I set myself a little project, making a word processor around a richtextbox control with a few extra features.
I'm currently just adding in the ability to 'Find & Replace' text, and the below code is working when used on the same form as the rich text box control.
richTextBox1.Rtf = richTextBox1.Rtf.Replace("bob", "bill");
I don't want to use a dialog box or something similar, i'm coming direct from our old friend VB6 though, so i'm not sure if they still even exist as such, so i'm making an external form that acts sort of like a dialog box, where i'd like the user to be able to enter the text to look for and replace and then press okay, and be sent back to the main form, sounds simple huh, probably is, i'm not sure what i'm missing...
private void findReplaceToolStripMenuItem_Click(object sender, EventArgs e)
{
Form3 AboutBox = new Form3();
AboutBox.ShowDialog();
}
I've tried my best at implementing a few of the answers I've read over here, in one of them i managed to be able to control form1 but only if i opened a new instance of it with form1.show(); after the code, which is kind of useless in what i'm trying to achieve.
I've set the richTextBox1.Modifiers to Public, but I'm still scratching my head over this one.
Instead of making the RichTextBox public, I'd add a property to the other form that returns the text from that control, like this:
public class SearchForm : Form
{
public string SearchTerm
{
get { return richTextBox1.Text; }
}
...
When the user closes the "search" form, you can get the search term by referencing the property:
private void findReplaceToolStripMenuItem_Click(object sender, EventArgs e)
{
string searchTerm;
using (var searchForm = new SearchForm()) // used 'using' to dispose the form
{
searchForm.ShowDialog();
searchTerm = searchForm.SearchTerm;
}
// do something with searchTerm
}
You'll find this makes maintenance more manageable too. Changing the names of controls in one form shouldn't require you to make changes in any other form that uses them.

How do you send data from a form to a class?

I have a Class named Testing and a Form called TitleScreen. In TitleScreen I have a textBox1 who's text I would like to be passed to a Class and then pass it back to my Form into a textBox2.
I know how to do only the basics in C# so if you try and make it simple as possible.
In your Class:
public class Class1
{
public static string SeparateName(string fullName)
{
string[] wordsInText = fullName.Split(' ');
return wordsInText[0];
}
}
In your Form:
private void button1_Click(object sender, System.EventArgs e)
{
textBox2.Text = Class1.SeparateName(textBox1.Text);
}
"I highly recommend that you read a book or tutorial that targets new users, otherwise there will be holes in your understanding of the language and the frameworks."
It sounds like you want to perform an operation on the textbox's value and then print the result in another textbox.
You can write a method (function) that accepts an argument of type String and perform the operation in that method. The method can then set the Text property of the textbox to the result.
If you're asking how to input code in a winforms project, you can double-click the background of the form to reach its code. (At least in Visual Studio)
If you don't know how to do the above suggestions, I highly recommend that you read a book or tutorial that targets new users, otherwise there will be holes in your understanding of the language and the frameworks.
I would suggest you want to look at the concept of data binding, whereby you bind the controls on your forms to the properties of the underlying objects (instances of your classes).
Binding removes the need to write code to cross-load the data from the class into the form and back again, instead you can then say "text box 1 is bound to this property of my class". Then, when you update the value of the textbox the data is automatically placed into the chosen property of your class instance. Typically you then have a save button that calls a save method on your class to persist the data to your data store (database or whatever).
It is perfectly reasonable to bind more than one control on your form to the same property on your underlying class, so in your example you can bind both textBox1 and textBox2 to the same property on your class. Then, once you've implemented databinding, when you change the value in textBox1, the value will automatically be reflected in textBox2, either on each keystroke or when the field is validated (typically when you move focus to another control).
This is the microsoft documentation on Winforms binding which covers everything you need: https://msdn.microsoft.com/en-us/library/ef2xyb33(v=vs.110).aspx

Categories