A little background: I'm currently writing a sample project using Winforms/C# that emulates Conway's Game of Life. Part of this sample involves UI Automation using the White Automation Framework. The relevant layout of the form includes a custom grid control for setting up the world and a list box control that displays/stores past generations of the world.
I have a World object that stores a list of Cell objects and calculates the next generation of a World from its current state:
public class World
{
public IReadOnlyCollection<Cell> Cells { get; private set; }
public World(IList<Cell> seed)
{
Cells = new ReadOnlyCollection<Cell>(seed);
}
public World GetNextGeneration()
{
/* ... */
}
}
In my UI, when I calculate the next world generation, the past generations list is updated. The past generation list stores World objects as its items, and I have subscribed to the Format event of the list box to format the item display. _worldProvider.PreviousGenerations is a collection of World objects.
private void UpdatePastGenerationsList()
{
GenerationList.SuspendLayout();
GenerationList.Items.Add(_worldProvider.PreviousGenerations.Last());
GenerationList.SelectedItem = _worldProvider.PreviousGenerations.Last();
GenerationList.ResumeLayout();
}
From this snippet you can see that the items of the ListBox are World objects. What I want to do in my test code is get the actual World object (or some representation of it) from the selected ListBox item, and then compare it to the grid's representation of the world. The grid has a full automation implementation so I can easily get a representation of the grid using existing automation calls in White.
The only idea I had was to make a derived ListBox control that sends an ItemStatus property changed automation event when the selected index changes from an automation click event, and then listening for that ItemStatus event in the test code. The World is first converted to a string (WorldSerialize.SerializeWorldToString) where each live cell is converted to formatted coordinates {x},{y};.
public class PastGenerationListBox : ListBox
{
public const string ITEMSTATUS_SELECTEDITEMCHANGED = "SelectedItemChanged";
protected override void OnSelectedIndexChanged(EventArgs e)
{
FireSelectedItemChanged(SelectedItem as World);
base.OnSelectedIndexChanged(e);
}
private void FireSelectedItemChanged(World world)
{
if (!AutomationInteropProvider.ClientsAreListening)
return;
var provider = AutomationInteropProvider.HostProviderFromHandle(Handle);
var args = new AutomationPropertyChangedEventArgs(
AutomationElementIdentifiers.ItemStatusProperty,
ITEMSTATUS_SELECTEDITEMCHANGED,
WorldSerialize.SerializeWorldToString(world));
AutomationInteropProvider.RaiseAutomationPropertyChangedEvent(provider, args);
}
}
The problem I have with this is that the event handler code in the test class is never being called. I think the problem is with the AutomationInteropProvider.HostProviderFromHandle call returning a different provider object from the one in the test code, but I am not sure.
My questions are:
Is there a better approach I can take, such as something provided by the MS Automation API?
If not - is there a way I can get the default C# IRawElementProviderSimple implementation for the ListBox control (to raise the Property Changed event)? I would rather not re-implement it just for this little bit of functionality.
Here is the code from the test side, which adds the listener for ItemStatusProperty change event. I am using SpecFlow for BDD which defines ScenarioContext.Current as a dictionary. WorldGridSteps.Window is a TestStack.White.Window object.
private static void HookListItemStatusEvent()
{
var list = WorldGridSteps.Window.Get<ListBox>(GENERATION_LIST_NAME);
Automation.AddAutomationPropertyChangedEventHandler(list.AutomationElement,
TreeScope.Element,
OnGenerationSelected,
AutomationElementIdentifiers.ItemStatusProperty);
}
private static void UnhookListItemStatusEvent()
{
var list = WorldGridSteps.Window.Get<ListBox>(GENERATION_LIST_NAME);
Automation.RemoveAutomationPropertyChangedEventHandler(list.AutomationElement, OnGenerationSelected);
}
private static void OnGenerationSelected(object sender, AutomationPropertyChangedEventArgs e)
{
if (e.EventId.Id != AutomationElementIdentifiers.ItemStatusProperty.Id)
return;
World world = null;
switch (e.OldValue as string)
{
case PastGenerationListBox.ITEMSTATUS_SELECTEDITEMCHANGED:
world = WorldSerialize.DeserializeWorldFromString(e.NewValue as string);
break;
}
if (world != null)
{
if (ScenarioContext.Current.ContainsKey(SELECTED_WORLD_KEY))
ScenarioContext.Current[SELECTED_WORLD_KEY] = world;
else
ScenarioContext.Current.Add(SELECTED_WORLD_KEY, world);
}
}
I was able to work around this problem by using non-persisted memory mapped files to allow additional communication between the window GUI and the test process.
This ended up being much easier than trying to completely re-write IRawElementProviderSimple implementations for both my "custom" ListBox and the items contained within.
My custom ListBox ended up looking like this:
public class PastGenerationListBox : ListBox
{
public const string SELECTEDWORLD_MEMORY_NAME = "SelectedWorld";
public const string SELECTEDWORLD_MUTEX_NAME = "SelectedWorldMutex";
private const int SHARED_MEMORY_CAPACITY = 8192;
private MemoryMappedFile _sharedMemory;
private Mutex _sharedMemoryMutex;
public new World SelectedItem
{
get { return base.SelectedItem as World; }
set { base.SelectedItem = value; }
}
public PastGenerationListBox()
{
_sharedMemory = MemoryMappedFile.CreateNew(SELECTEDWORLD_MEMORY_NAME, SHARED_MEMORY_CAPACITY);
_sharedMemoryMutex = new Mutex(false, SELECTEDWORLD_MUTEX_NAME);
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
WriteSharedMemory(SelectedItem);
base.OnSelectedIndexChanged(e);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_sharedMemoryMutex.WaitOne();
if (_sharedMemory != null)
_sharedMemory.Dispose();
_sharedMemory = null;
_sharedMemoryMutex.ReleaseMutex();
if (_sharedMemoryMutex != null)
_sharedMemoryMutex.Dispose();
_sharedMemoryMutex = null;
}
base.Dispose(disposing);
}
private void WriteSharedMemory(World world)
{
if (!AutomationInteropProvider.ClientsAreListening) return;
var data = WorldSerialize.SerializeWorldToString(world);
var bytes = Encoding.ASCII.GetBytes(data);
if (bytes.Length > 8188)
throw new Exception("Error: the world is too big for the past generation list!");
_sharedMemoryMutex.WaitOne();
using (var str = _sharedMemory.CreateViewStream(0, SHARED_MEMORY_CAPACITY))
{
str.Write(BitConverter.GetBytes(bytes.Length), 0, 4);
str.Write(bytes, 0, bytes.Length);
}
_sharedMemoryMutex.ReleaseMutex();
}
}
My test code looks like this:
private static World GetWorldFromMappedMemory()
{
string str;
using (var mut = Mutex.OpenExisting(PastGenerationListBox.SELECTEDWORLD_MUTEX_NAME))
{
mut.WaitOne();
using (var sharedMem = MemoryMappedFile.OpenExisting(PastGenerationListBox.SELECTEDWORLD_MEMORY_NAME))
{
using (var stream = sharedMem.CreateViewStream())
{
byte[] rawLen = new byte[4];
stream.Read(rawLen, 0, 4);
var len = BitConverter.ToInt32(rawLen, 0);
byte[] rawData = new byte[len];
stream.Read(rawData, 0, rawData.Length);
str = Encoding.ASCII.GetString(rawData);
}
}
mut.ReleaseMutex();
}
return WorldSerialize.DeserializeWorldFromString(str);
}
Related
So as part of my training I have to create a program which utilizes the Xbox Api. It works so that the user searches for a Gamer tag and it brings up all of that users recently uploaded game clips. You can then select a clip and it will play within the Windows Media Player control I added.
Now I am trying to set it to auto play the next video. How would I fill out the following method to autoplay the next item in the list keeping in mind that it is not looping through all the videos in the list, just from where the user selects from.
private void wmpClip_PlayStateChange(object sender,
AxWMPLib._WMPOCXEvents_PlayStateChangeEvent e)
{
if (wmpClip.playState == WMPLib.WMPPlayState.wmppsMediaEnded)
{
//play next video in list
}
}
So the following code is part of the search method when searching for clips:
foreach (var video in videos)
{
ctrlSearchResults searchResult = new ctrlSearchResults();
searchResult.SetDetails(video);
flpSearchResults.Controls.Add(searchResult);
searchResult.OnVideoSelected += SearchResult_OnVideoSelected;
//Limit to save api requests
if (flpSearchResults.Controls.Count == 3)
{
break;
}
}
Then when the user clicks on a video from the list on the Flow Layout Panel, the following code is run:
private void SearchResult_OnVideoSelected(Video obj)
{
wmpClip.Visible = true;
wmpClip.URL = obj.gameClipUris[0].uri;
pnlVideoInfo.Visible = true;
lblClipName.Visible = true;
lblActualLength.Text = obj.durationInSeconds.ToString();
lblActualSize.Text = (obj.gameClipUris[0].fileSize / 1024 / 1024).ToString() + "mb";
lblActualDate.Text = obj.datePublished.ToString();
lblActualGame.Text = obj.titleName;
lblActualViews.Text = obj.views.ToString();
lblActualRating.Text = obj.rating.ToString();
lblActualLikes.Text = obj.likeCount.ToString();
lblClipName.Text = obj.clipName;
GamerCard gamer = _clipsApi.GetGamerCardByXUID(obj.xuid.ToString());
pnlGamerInfo.Visible = true;
pbGamerPic.Load(gamer.gamerpicLargeSslImagePath);
lblGamerTag.Text = gamer.gamertag;
lblGamerScore.Text = gamer.gamerscore.ToString();
lblActualLocation.Text = gamer.location;
txtBio.Text = gamer.bio;
}
I hope this makes sense.
You should work on your separation of concerns: divide your big problem into smaller problems, and invent for every smaller problem a separate fairly independent solution. This makes the implementations easier to understand, easier to reust, test, change etc.
So let's separate your concerns!
Apparently you have something to fetch all your videos as a sequence:
IEnumerable<Video> GetAllVideos() {...}
How this is implemented is up to you. I assume you have no duplicates in this: every Video is either selected or not selected, you can not have the same video selected as well as non-selected.
Let's create a collection class for videos where you can select and unselect videos.
In fact: let's make it reusable: a generic collection class that contains objects that
can be selected and unselected:
public SelectableCollection<T>
// only if desired
: IReadonlyCollection<T>, IReadonlyList<T>, IEnumerable<T>
{
private readonly Dictionary<T, bool> allItems;
public SelectableCollection() : this(Enumerable.Empty<T>()) {}
public SelectableCollection(IEnumerable<T> collection) : this (collection, null) {}
public SelectableCollection(IEnumerable<T> source, IEqualityComparer<T> comparer = null)
{
// TODO: check source not null
if (comparer == null) comparer = EqualityComparer<T>.Default;
// initially nothing selected:
this.AllItems = source.ToDictionary(video => video, false, comparer);
}
Select and Unselect:
bool IsSelected(T item)
{
// TODO: decide what if item does not exist
return this.allItems[item];
}
bool Select(T item)
{
// TODO: decide what if item does not exist
this.allItems[item] = true;
}
bool UnSelect(T item)
{
// TODO: decide what if item does not exist
this.allItems[item] = false;
}
IEnumerable<T> SelectedItems => this.allItems
// Key is T, Value is boolean Selected
.Where(keyValuePair => keyValuePair.Value)
.Select(keyValuePair => keyValuePair.Key);
TODO: implement IReadOnlyCollection, etc. Use this.allItems.Keys to get all items.
Your forms class:
private SelectableCollection<Video> videos = new SelectableCollection(this.GetAllVideos());
Select and Unselect:
bool IsSelected(Video video)
{
return this.videos.IsSelected(video);
}
bool Select(Video video)
{
this.videos.Select(video);
}
bool UnSelect(Video video)
{
this.videos.UnSelect(video);
}
IEnumerable<Video> SelectedVideos => this.videos.SelectedItems;
Start / Stop / Continue playing Videos:
class VideoPlayer
{
private List<Video> VideosToPlay {get; set;}
private int IndexNextVideoToPlay {get; set;}
void StartPlayingVideos(IEnumerable<Video> videos)
{
this.VideosToPlay = this.SelectedVideos.ToList();
this.IndexNextVideoToPlay = 0;
}
private void PlayNextVideo()
{
if (this.IndexNextVideoToPlay < this.VideosToPlay.Count)
{
this.Play(this.VideosToPlay[this.IndexNextVideoToPlay];
++this.IndexNextVideoToPlay;
}
}
}
Examples:
private void OnButtonClicked(object sender, EventArgs e)
{
// user finished selecting videos in the listbox. Mark them as selected
IEnumerable<Video> selectedVideos = this.listBox1.SelectedItems.Cast<Video>();
// TODO: select all selectedVideos; Unselect all not-selected videos.
}
private void OnVideoFinished(object sender, EventArgs e)
{
this.VideoPlayer.PlayNextVideo();
}
Conclusion: divide your problem into small sub problems. Don't hesitate to create a class that solves your sub problem. Usually these classes will only have one small task and barely depend on other classes. Hence your class will be easy to understande, easy to create, easy to reuse, easy to test and maintain.
I want bind the master sound volume of windows to a slider in my program. So I searched and found some ways to GET or SET master volume + some libraries like these:
Change master audio volume from XP to Windows 8 in C#
Handling volume control in C#
NAudio
Some where I see a code with LOOP to get volume value: loop
Some where I see a code with TIMER to get volume value...
Also I see some samples to determine volume but after test one of them I saw some errors at runtime in Windows 8: C# – Adjust master volume in Vista and Windows 7
EDIT:
Now i have the following class. i create an instance of it and use propertchange event to show volume by Trace.WriteLine. but when i change the windows volume it cause an unhandeled error!
public class AudioEndpointVolumeEnforcer : INotifyPropertyChanged
{
private MMDeviceEnumerator mmDeviceEnumerator;
private MMDevice mmDevice;
private AudioEndpointVolume audioEndpointVolume;
private bool _deviceIsMuted;
private int _desiredVolume;
private int _volumePercent;
public AudioEndpointVolumeEnforcer()
{
try
{
mmDeviceEnumerator = new MMDeviceEnumerator();
mmDevice = mmDeviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
audioEndpointVolume = mmDevice.AudioEndpointVolume;
audioEndpointVolume.OnVolumeNotification += data =>
{
VolumePercent = Convert.ToInt16(data.MasterVolume*100);
_deviceIsMuted = data.Muted;
};
DesiredVolume = 65;
}
catch (Exception ex)
{
// Logging logic here
}
}
public int DesiredVolume
{
get { return _desiredVolume; }
private set
{
if (_desiredVolume == value) return;
_desiredVolume = value;
//NotifyOfPropertyChange();
OnPropertyChanged("DesiredVolume");
Enforce(_desiredVolume);
}
}
public int VolumePercent
{
get { return _volumePercent; }
private set
{
if (_volumePercent == value) return;
_volumePercent = value;
if (_volumePercent != _desiredVolume)
{
_volumePercent = _desiredVolume;
Enforce(_volumePercent);
}
}
}
public void Enforce(int pct, bool mute = false)
{
var adjusted = Convert.ToInt16(audioEndpointVolume.MasterVolumeLevelScalar*100);
if (adjusted != DesiredVolume)
{
audioEndpointVolume.MasterVolumeLevelScalar = pct/100f;
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Use the class:
// Inside my window cunstractor >>
audioVolume = new AudioEndpointVolumeEnforcer();
audioVolume.PropertyChanged += MasterAudioVolumeChanged;
private void MasterAudioVolumeChanged(object obj, PropertyChangedEventArgs eventArgs)
{
Trace.WriteLine(eventArgs.PropertyName+" - "+audioVolume.DesiredVolume);
}
Runtime Error:
......................................
The Output panel show Access violation error:
The program '[18488] Audio.exe' has exited with code -1073741819 (0xc0000005) 'Access violation'
Edit
I tested the above code by breakpoints and trace. the above error happens sometimes in the bellow part:
audioEndpointVolume.OnVolumeNotification += data =>
{
VolumePercent = Convert.ToInt16(data.MasterVolume*100);
_deviceIsMuted = data.Muted;
};
For example sometimes it happens in this line:
_deviceIsMuted = data.Muted;
But when i go to the next step by F11 it dose not show a normal error inside the program! It cause the bellow error window and application force closing!
......................................
Access violation
You can use the NAudio library as such:
using NAudio;
using NAudio.CoreAudioApi;
private static MMDeviceEnumerator enumer = new MMDeviceEnumerator();
private MMDevice dev = enumer.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
public void Form1_Load(object sender, EventArgs e){
dev.AudioEndpointVolume.OnVolumeNotification += AudioEndpointVolume_OnVolumeNotification;
}
void AudioEndpointVolume_OnVolumeNotification(AudioVolumeNotificationData data)
{
// This shows data.MasterVolume, you can do whatever you want here
MessageBox.Show(data.MasterVolume.ToString());
}
This example uses a WinForms project, but you could also create the event elsewhere.
NAudio can be installed as a NuGet package, or from https://naudio.codeplex.com/
The volume ranges from 0.0f to 1.0f, to get 0-100 simply do:
(int)(data.MasterVolume * 100f)
Does your code update a control in response to the event, say a trackbar or a mute button? If so, you may need to use a thread-safe approach. Controls can only be updated by the UI thread. Me.InvokeRequired checks to see if it's the UI thread that wants to do the update. If not, it returns True. Me.Invoke then uses a delegate to handle communication between the two threads. If you don't use Me.Invoke, there will be an access violation.
Delegate Sub UpdateCallback(Volume As Integer, Muted As Boolean)
Public Overloads Sub Update(Volume As Integer, Muted As Boolean)
If tbVolume.InvokeRequired Then
Dim d As New UpdateCallback(AddressOf Update)
Me.Invoke(d, Volume, Muted)
Else
tbVolume.Value = Volume
_Mute = Muted
btnMuteUnmute.BackgroundImage = DirectCast(If(_Mute, _
My.Resources.mute, My.Resources.Unmute), Icon).ToBitmap
End If
End Sub
I am working in Visual Studio 2010 .NET 4.0 in C# using WinForms.
The form has a single DataGridView that is data bound to a DataSet.
The DataSet is being populated from a Thread that is processing data being read from a ConcurrentQueue.
The code is also using a semaphore to serialize access to the DataSet as it can be accessed from the "worker" Thread and the UI main thread ( when updating the DataGridView ).
The UI/Form has a System.Windows.Forms.Timer that fires every 1/4 second to call a function that causes the DataGridView to be updated with the current contents of the DataSet.
The code runs correctly until the point at which the DataGridView data scrolls and the scroll bar becomes visible. At this point the entire form becomes unresponsive - title caption says "Program Not Responding".
The interesting thing is that this DOESN'T happen while running under the debugger. Only when the program is deployed. And no exceptions are ever raised.
I've tried using both Invoke and BeginInvoke with no change in behavior.
Questions:
1- What might be wrong with my code below?
2- Since I can't observe this behavior while running under the debugger, how do I find out what is causing the problem?
The wall of code is provided below. I know it's a lot of code, but I cut out as much as I could.
// deleted using statements for brevity
namespace namcom
{
public partial class wndXMLtrans : Form
{
private DataSet dsRecords = new DataSet();
private Thread threadConsumeXML = null;
private static Semaphore ResourceLock;
public wndXMLtrans(String ip)
{
InitializeComponent();
ResourceLock = new Semaphore(1, 1);
curSize = this.Size;
m_ip = ip;
}
private Boolean AddRowToDataSet(String[] columns, String xml)
{
Boolean retCode = true;
String value = String.Empty;
Int64 id = -1;
DataRow row;
ResourceLock.WaitOne();
row = dsRecords.Tables[0].NewRow();
// prepare row code omitted - brevity
// add new data row to DataSet
dsRecords.Tables[0].Rows.Add(row);
ResourceLock.Release();
// SQL inserts into DB removed - brevity
return (retCode);
}
private Boolean HandleSingleXMLMessage(String[] columns, String xml, out String exceptionMessage)
{
Boolean boolRet = true;
exceptionMessage = String.Empty;
// store data in dataset and database
if ( closeRequested == false )
{
AddRowToDataSet(columns, xml);
}
return (boolRet);
}
private Boolean StoreG2SMessages(String message)
{
// code removed - brevity
// removed code just parses out string and in a loop calls HandleSingleXMLMessage
// until all XML in message have been processed
HandleSingleXMLMessage(columns,xml, out exceptionMessage) // call in loop
return (ret);
}
// pull XML out of mainwnd.msgQueue and update database
private void G2SParseThread()
{
String Data;
String exceptionMsg = String.Empty;
while ( /* thread is to be active - code removed for brevity */)
{
Data = String.Empty;
if (mainwnd.msgQueue.TryDequeue(out Data) == true)
{
this.StoreG2SMessages(Data);
}
Thread.Sleep(20);
}
// thread ended cleanup code removed - brevity
}
private void StartThreads()
{
// start XML packet processing thread
threadConsumeXML = new Thread(G2SParseThread);
threadConsumeXML.SetApartmentState(ApartmentState.STA);
threadConsumeXML.Start();
threadMonitor = new Thread(() => threadHandleStatusMsg(ref mainwnd.statusQueue));
threadMonitor.Start();
}
private void wndXMLtrans_Shown(object sender, EventArgs e)
{
// remove SQL code - brevity
// fill dsRecords ( DataSet )
try
{
var adapter = new SQLiteDataAdapter(selectSQL, dbConnection);
adapter.Fill(dsRecords);
dataGrid.DataSource = dsRecords.Tables[0].DefaultView;
adapter.Dispose();
}
catch { } // catch code removed - brevity
StartThreads();
}
private void gridUpdateTimer_Tick(object sender, EventArgs e)
{
ResourceLock.WaitOne();
try
{
if (dataGrid != null && numRows < dsRecords.Tables[0].Rows.Count)
{
if (dataGrid.RowCount > 0)
{
dataGrid.FirstDisplayedScrollingRowIndex = dataGrid.RowCount - 1;
}
dataGrid.Refresh();
numRows = dsRecords.Tables[0].Rows.Count;
}
}
catch { }
ResourceLock.Release();
}
}
}
EDIT: Hans provided the answer which is that you can't update a bound DataSet from a worker thread. So I was able to fix the problem by adding the following:
These are called by delegate functions using Invoke. Unbind called before DataSet update and Bind after DataSet is updated. This works, but causes the DataGridView to appear to flicker or redraw multiple times. Any way to remedy this?
public void UnbindDataGridView(DataGridView grid)
{
grid.DataSource = null;
}
public void BindDataGridView(DataGridView grid)
{
grid.DataSource = dsRecords.Tables[0].DefaultView;
}
What is the proper way to change Form language at runtime?
Setting all controls manually using recursion like this
Save language choice to file > Restart Application > Load languge
choice before InitializeComponent();
Using Form constructor to replace instance of active from (if this is even possible)
Something else
There is so much half written threads about this but none provides real answer on what is proper way to do this?
UPDATE:
To clarify my question:
Doing something like this:
public Form1()
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de");
this.InitializeComponent();
}
works fine and all my controls and everything else in resources get translated correctly.
And doing something like:
private void button1_Click(object sender, EventArgs e)
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en");
}
does nothing, Form stays in language I set up before InitializeComponent();
I believe the solution shown in Hans Passant's comment might be the only (general) solution.
Personally, I use this base class for all forms that need to be localized:
public class LocalizedForm : Form
{
/// <summary>
/// Occurs when current UI culture is changed
/// </summary>
[Browsable(true)]
[Description("Occurs when current UI culture is changed")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
[Category("Property Changed")]
public event EventHandler CultureChanged;
protected CultureInfo culture;
protected ComponentResourceManager resManager;
/// <summary>
/// Current culture of this form
/// </summary>
[Browsable(false)]
[Description("Current culture of this form")]
[EditorBrowsable(EditorBrowsableState.Never)]
public CultureInfo Culture
{
get { return this.culture; }
set
{
if (this.culture != value)
{
this.ApplyResources(this, value);
this.culture = value;
this.OnCultureChanged();
}
}
}
public LocalizedForm()
{
this.resManager = new ComponentResourceManager(this.GetType());
this.culture = CultureInfo.CurrentUICulture;
}
private void ApplyResources(Control parent, CultureInfo culture)
{
this.resManager.ApplyResources(parent, parent.Name, culture);
foreach (Control ctl in parent.Controls)
{
this.ApplyResources(ctl, culture);
}
}
protected void OnCultureChanged()
{
var temp = this.CultureChanged;
if (temp != null)
temp(this, EventArgs.Empty);
}
}
Then instead of directly changing Thread.CurrentThread.CurrentUICulture, I use this property in static manager class to change UI culture:
public static CultureInfo GlobalUICulture
{
get { return Thread.CurrentThread.CurrentUICulture; }
set
{
if (GlobalUICulture.Equals(value) == false)
{
foreach (var form in Application.OpenForms.OfType<LocalizedForm>())
{
form.Culture = value;
}
Thread.CurrentThread.CurrentUICulture = value;
}
}
}
I have found another way:
Move initialization form code in a private method like below:
private void FormInitialize() {/*Your code here*/}
In the form constructor use it like this:
public Form1()
{
InitializeComponent();
FormInitialize();
}
And from Button, menuItem or other call method like this:
private void ChangeCultureToFrench_Click(object sender, EventArgs e)
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr");
this.Controls.Clear();
this.InitializeComponent();
FormInitialize();
}
I hope this helps ;-)
I've discovered this kind of approach a few minutes ago. Just quick and simple restart of the main form. Meybe it will help to someone. Event is created inside the form on my own, called when user selects the language from menu but after the selected culture's name is saved into the settings. Culture names are then loaded from that settings. Works exactly as I need and looks like proper solution.
static class Program
{
private static bool doNotExit = true;
private static FormMain fm;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
while(doNotExit)
{
System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo(Properties.Settings.Default.language);//
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(Properties.Settings.Default.language);//
doNotExit = false;
fm = new FormMain();
fm.lanugageChangedEvent += new EventHandler(main_LanugageChangedEvent);
Application.Run(fm);
}
}
static void main_LanugageChangedEvent(object sender, EventArgs e)
{
doNotExit = true;
fm.Close();
}
}
In reference to your ColumnHeader .NET framework bug, I also discovered this bug recently and posted a question about it (to which I haven't received any responses). I was able to fix the problem by hardcoding the changes for the ColumnHeaders. For example:
resources.ApplyResources(_myHeader, "_myHeader", culture);
You basically just replace the call to .Name with a literal string of the name. I have tested this and it works. Unfortunately this means it won't fit as part of the code you use to change all of the controls. You will have to add a line for each ColumnHeader object you need to change. If you have a listview with a variable number of columns, that could get tricky. Another option is to create localized resource files. I assume you probably already have them for stuff like message box text and other strings. Then you can add an entry in your resource file like "columnHeader_myHeader" and set the appropriate language text for each language. Finally, you can manually change the text to your column headers by using:
_myHeader.Text = myResourceFileName.columnHeader_myHeader;
This will select the appropriate language based on the current thread culture.
Hans was correct in that there doesn't seem to be a foolproof "proper" way to perform localization in .NET, though there are a variety of tools you can use. For something like a job application, even though it is probably already too late for this advice, my suggestion would be to learn as many different methods as you can for localization, learn the pros and cons, and then just pick a system and be able to argue why you believe it is the "proper" choice. They are probably more concerned with your logic and reasoning and demonstration of prior experience than they are with the actual method.
Hope this would help anyone, I found it best for me cause i needed to change buttons location according the lang (browse on the right or left of the search box and labels next to text fields).
save a public var on the main that will hold the lang.
created a class which handles the visual part
created xml files that will hold any language data and more (in my xml tag name=object name).
sent that class's constructor the form (to save and work with)
connect to that current xml file
From main form call whenever you want to initialView (part of the view class) and change lang (and more) anytime (just connect to the right xml file):
public void initialView()
{
//Set rightToLeft values
initialIndent(mainForm);
//set visual text values
initialTxt();
}
private void initialTxt()
{
// Are there any more controls under mainObj (Form1)?
Boolean endOfElemsUnderPnl = false;
// The current Control im working on
Object curObj = mainForm;
do
{
// MenuStrip needs to be handled separately
if (typeof(MenuStrip).ToString().Equals(curObj.GetType().ToString()))
{
foreach (ToolStripMenuItem miBase in ((MenuStrip)(curObj)).Items)
{
miBase.Text = mainForm.dbCon.getData(miBase.Name.ToString());
foreach (ToolStripMenuItem miInnerNode in miBase.DropDownItems)
{
miInnerNode.Text = mainForm.dbCon.getData(miInnerNode.Name.ToString());
}
}
}
// Any other Control i have on the form
else
{
((Control)(curObj)).Text = mainForm.dbCon.getData(((Control)(curObj)).Name.ToString());
}
curObj = mainForm.GetNextControl(((Control)(curObj)), true);
// Are there any more controls under mainObj?
if (null == curObj)
{
endOfElemsUnderPnl = true;
}
} while (!endOfElemsUnderPnl);
}
private void initialIndent(frmMyFileManager parent)
{
if (parent.Language.Equals("Hebrew"))
{
parent.RightToLeft = RightToLeft.Yes;
}
else if (parent.Language.Equals("English"))
{
parent.RightToLeft = RightToLeft.No;
}
else
{
parent.RightToLeft = RightToLeft.No;
}
}
And this is an example of how easy it is for my at runtime:
private void selectLanguageToolStripMenuItem_Click(object sender, EventArgs e)
{
DialogResult res = MessageBox.Show(this, "click yes for english and no for hebrew", "Select language", MessageBoxButtons.YesNoCancel);
if (DialogResult.Yes == res)
{
Language = "English";
}
else if (DialogResult.No == res)
{
Language = "Hebrew";
}
dbCon = new CDBConnector("****\\lang" + Language + ".xml");
view.initialView();
}
What would be the best way to develop a text box that remembers the last x number of entries that were put into it. This is a standalone app written with C#.
This is actually fairly easy, especially in terms of showing the "AutoComplete" part of it. In terms of remembering the last x number of entries, you are just going to have to decide on a particular event (or events) that you consider as an entry being completed and write that entry off to a list... an AutoCompleteStringCollection to be precise.
The TextBox class has the 3 following properties that you will need:
AutoCompleteCustomSource
AutoCompleteMode
AutoCompleteSource
Set AutoCompleteMode to SuggestAppend and AutoCompleteSource to CustomSource.
Then at runtime, every time a new entry is made, use the Add() method of AutoCompleteStringCollection to add that entry to the list (and pop off any old ones if you want). You can actually do this operation directly on the AutoCompleteCustomSource property of the TextBox as long as you've already initialized it.
Now, every time you type in the TextBox it will suggest previous entries :)
See this article for a more complete example: http://www.c-sharpcorner.com/UploadFile/mahesh/AutoCompletion02012006113508AM/AutoCompletion.aspx
AutoComplete also has some built in features like FileSystem and URLs (though it only does stuff that was typed into IE...)
#Ethan
I forgot about the fact that you would want to save that so it wasn't a per session only thing :P But yes, you are completely correct.
This is easily done, especially since it's just basic strings, just write out the contents of AutoCompleteCustomSource from the TextBox to a text file, on separate lines.
I had a few minutes, so I wrote up a complete code example...I would've before as I always try to show code, but didn't have time. Anyway, here's the whole thing (minus the designer code).
namespace AutoComplete
{
public partial class Main : Form
{
//so you don't have to address "txtMain.AutoCompleteCustomSource" every time
AutoCompleteStringCollection acsc;
public Main()
{
InitializeComponent();
//Set to use a Custom source
txtMain.AutoCompleteSource = AutoCompleteSource.CustomSource;
//Set to show drop down *and* append current suggestion to end
txtMain.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
//Init string collection.
acsc = new AutoCompleteStringCollection();
//Set txtMain's AutoComplete Source to acsc
txtMain.AutoCompleteCustomSource = acsc;
}
private void txtMain_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
//Only keep 10 AutoComplete strings
if (acsc.Count < 10)
{
//Add to collection
acsc.Add(txtMain.Text);
}
else
{
//remove oldest
acsc.RemoveAt(0);
//Add to collection
acsc.Add(txtMain.Text);
}
}
}
private void Main_FormClosed(object sender, FormClosedEventArgs e)
{
//open stream to AutoComplete save file
StreamWriter sw = new StreamWriter("AutoComplete.acs");
//Write AutoCompleteStringCollection to stream
foreach (string s in acsc)
sw.WriteLine(s);
//Flush to file
sw.Flush();
//Clean up
sw.Close();
sw.Dispose();
}
private void Main_Load(object sender, EventArgs e)
{
//open stream to AutoComplete save file
StreamReader sr = new StreamReader("AutoComplete.acs");
//initial read
string line = sr.ReadLine();
//loop until end
while (line != null)
{
//add to AutoCompleteStringCollection
acsc.Add(line);
//read again
line = sr.ReadLine();
}
//Clean up
sr.Close();
sr.Dispose();
}
}
}
This code will work exactly as is, you just need to create the GUI with a TextBox named txtMain and hook up the KeyDown, Closed and Load events to the TextBox and Main form.
Also note that, for this example and to make it simple, I just chose to detect the Enter key being pressed as my trigger to save the string to the collection. There is probably more/different events that would be better, depending on your needs.
Also, the model used for populating the collection is not very "smart." It simply deletes the oldest string when the collection gets to the limit of 10. This is likely not ideal, but works for the example. You would probably want some sort of rating system (especially if you really want it to be Google-ish)
A final note, the suggestions will actually show up in the order they are in the collection. If for some reason you want them to show up differently, just sort the list however you like.
Hope that helps!
I store the completion list in the registry.
The code I use is below. It's reusable, in three steps:
replace the namespace and classname in this code with whatever you use.
Call the FillFormFromRegistry() on the Form's Load event, and call SaveFormToRegistry on the Closing event.
compile this into your project.
You need to decorate the assembly with two attributes: [assembly: AssemblyProduct("...")] and [assembly: AssemblyCompany("...")] . (These attributes are normally set automatically in projects created within Visual Studio, so I don't count this as a step.)
Managing state this way is totally automatic and transparent to the user.
You can use the same pattern to store any sort of state for your WPF or WinForms app. Like state of textboxes, checkboxes, dropdowns. Also you can store/restore the size of the window - really handy - the next time the user runs the app, it opens in the same place, and with the same size, as when they closed it. You can store the number of times an app has been run. Lots of possibilities.
namespace Ionic.ExampleCode
{
public partial class NameOfYourForm
{
private void SaveFormToRegistry()
{
if (AppCuKey != null)
{
// the completion list
var converted = _completions.ToList().ConvertAll(x => x.XmlEscapeIexcl());
string completionString = String.Join("¡", converted.ToArray());
AppCuKey.SetValue(_rvn_Completions, completionString);
}
}
private void FillFormFromRegistry()
{
if (!stateLoaded)
{
if (AppCuKey != null)
{
// get the MRU list of .... whatever
_completions = new System.Windows.Forms.AutoCompleteStringCollection();
string c = (string)AppCuKey.GetValue(_rvn_Completions, "");
if (!String.IsNullOrEmpty(c))
{
string[] items = c.Split('¡');
if (items != null && items.Length > 0)
{
//_completions.AddRange(items);
foreach (string item in items)
_completions.Add(item.XmlUnescapeIexcl());
}
}
// Can also store/retrieve items in the registry for
// - textbox contents
// - checkbox state
// - splitter state
// - and so on
//
stateLoaded = true;
}
}
}
private Microsoft.Win32.RegistryKey AppCuKey
{
get
{
if (_appCuKey == null)
{
_appCuKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(AppRegistryPath, true);
if (_appCuKey == null)
_appCuKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(AppRegistryPath);
}
return _appCuKey;
}
set { _appCuKey = null; }
}
private string _appRegistryPath;
private string AppRegistryPath
{
get
{
if (_appRegistryPath == null)
{
// Use a registry path that depends on the assembly attributes,
// that are presumed to be elsewhere. Example:
//
// [assembly: AssemblyCompany("Dino Chiesa")]
// [assembly: AssemblyProduct("XPathVisualizer")]
var a = System.Reflection.Assembly.GetExecutingAssembly();
object[] attr = a.GetCustomAttributes(typeof(System.Reflection.AssemblyProductAttribute), true);
var p = attr[0] as System.Reflection.AssemblyProductAttribute;
attr = a.GetCustomAttributes(typeof(System.Reflection.AssemblyCompanyAttribute), true);
var c = attr[0] as System.Reflection.AssemblyCompanyAttribute;
_appRegistryPath = String.Format("Software\\{0}\\{1}",
p.Product, c.Company);
}
return _appRegistryPath;
}
}
private Microsoft.Win32.RegistryKey _appCuKey;
private string _rvn_Completions = "Completions";
private readonly int _MaxMruListSize = 14;
private System.Windows.Forms.AutoCompleteStringCollection _completions;
private bool stateLoaded;
}
public static class Extensions
{
public static string XmlEscapeIexcl(this String s)
{
while (s.Contains("¡"))
{
s = s.Replace("¡", "¡");
}
return s;
}
public static string XmlUnescapeIexcl(this String s)
{
while (s.Contains("¡"))
{
s = s.Replace("¡", "¡");
}
return s;
}
public static List<String> ToList(this System.Windows.Forms.AutoCompleteStringCollection coll)
{
var list = new List<String>();
foreach (string item in coll)
{
list.Add(item);
}
return list;
}
}
}
Some people shy away from using the Registry for storing state, but I find it's really easy and convenient. If you like, You can very easily build an installer that removes all the registry keys on uninstall.