MVVM Observable collection issue - c#

I am creating A WPF application and am currently having issues with updating the visuals for a particular instance.
I have a ViewModel.Textlines which I am trying to change one element within that. It works and behaves fine from what I gather.
I am using Remove and Insert and it doesnt work. I use breakpoints and find out it all seems to work and the element has indeed been swapped out and when I check the OC it will show the values I wanted it to have and OnPropertyChanged Has been called after but it fails to update that. It might occure because the value is not a 'new' MFD_textline as I am just assigning it via =
Below is the code I am using
private void Controller_UpdateEvent(object sender, UpdateEventArgs e)
{
if(e.SystemName == "ALE47")
{
if(e.Values.ContainsKey("PageIndex") && e.Values.ContainsKey("Value") && e.Values.ContainsKey("TextLine"))
{
int pageIndex = (int)e.Values["PageIndex"].NewValue;
int value = (int)e.Values["Value"].NewValue;
textLine = new MFD_Textline();
textLine = (MFD_Textline)e.Values["TextLine"].NewValue;
ViewModel.TextLines.RemoveAt(value);
ViewModel.TextLines.Insert(value, textLine);
}
}
}
public void TextLineChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// when the textlines obserevable collection is changed (added to or removed) force a property changed to update the view
if (e.Action == NotifyCollectionChangedAction.Add)
{
if (TextLines.Count == 11)
{
OnPropertyChanged("textLines");
}
}
}
private ObservableCollection<MFD_Textline> textLines;
public ObservableCollection<MFD_Textline> TextLines
{
get { return textLines; }
set
{
textLines = value;
OnPropertyChanged("textlines");
}
}
Please let me know if there are any questions or if I have not quite explained it right.

This call is wrong OnPropertyChanged("textlines");
You should pass the Property's name, not the backing field's
OnPropertyChanged("TextLines");

Related

store Edm.GeographyPoint types in Azure Search

Hi im trying to create a Edm.GeographyPoint item in an Azure Search Index via the Kentico CustomAzureSearchModule.This is activated on an index rebuild in Kentico. I keep getting the error message The value specified for the spatial property was not valid. You must specify a valid spatial value..
My code is as follows:
public class CustomAzureSearchModule : Module
{
private string nodeguid = "";
public CustomAzureSearchModule() : base(nameof(CustomAzureSearchModule))
{
}
protected override void OnInit()
{
base.OnInit();
DataMapper.Instance.RegisterMapping(typeof(GeographyPoint), Microsoft.Azure.Search.Models.DataType.GeographyPoint);
DocumentFieldCreator.Instance.CreatingField.After += CreatingField_After;
DocumentCreator.Instance.AddingDocumentValue.Execute += AddingValue;
}
private void CreatingField_After(object sender, CreateFieldEventArgs e)
{
if (e.SearchField.FieldName == "GeoLocation")
{
//Change the field type to Edm.GeographyPoint for Azure Search
e.Field = new Microsoft.Azure.Search.Models.Field("geolocation", Microsoft.Azure.Search.Models.DataType.GeographyPoint);
}
}
private void AddingValue(object sender, AddDocumentValueEventArgs e)
{
if (e.Document.ContainsKey("nodeguid"))
{
nodeguid = e.Document["nodeguid"].ToString(); //Store NodeGuid
}
//}
if (e.AzureName == "geolocation")
{
//Collect nodeGuid and use to get page
TreeNode page = DocumentHelper.GetDocuments()
.WhereEquals("NodeGUID", nodeguid)
.OnCurrentSite()
.Culture("en-gb")
.TopN(1)
.FirstOrDefault();
if (page != null)
{
// Check page type is a service only
if (page.ClassName == ServicePage.CLASS_NAME)
{
//Check for Children
if (page.Children.Count > 0)
{
e.Value = GeographyPoint.Create(31.8, -5); //Add location data to index
}
}
}
}
}
}
}
Would appreciate any help.
Would eventually like to create Collection(EDM.GeographyPoint) type in the Azure Index with multiple geocodes.
Followed this article to produce my code https://devnet.kentico.com/articles/customizing-azure-search-fields
Your code looks correct actually, but which version of Kentico are you using 11 or 12, and which version of the Azure Search API are you using ? Have you tried to cast the coord values to a Double to just be sure ?
Also, are you adding any SearchDocuments with a possible null value? (Like if page == null) ? What would happen if you just set e.Value = GeographyPoint.Create(31.8, -5) every time it iterates through that code just to see if the indexing works.

C# Removing Submenu Item Image Margins

See the linked screenshot below.
In short, I need those little white boxes to disappear - they're supposed to house an image, but there is no image, and so I'd rather they disappear.
I've accomplished this using the follow code:
foreach (ToolStripMenuItem menuItem in mnuMain.Items)
((ToolStripDropDownMenu)menuItem.DropDown).ShowImageMargin = false;
This works for what I guess are the main items, but not the sub-items, as you can see in the picture.
I've tried a few variations on the above code to try and get it to capture everything instead of just the first level items, but no luck.
What am I doing wrong?
http://i.imgur.com/bst1i4v.png
You should do that for sub items too. To do so, you can use this code:
private void Form1_Load(object sender, EventArgs e)
{
SetValuesOnSubItems(this.menuStrip1.Items.OfType<ToolStripMenuItem>().ToList());
}
private void SetValuesOnSubItems(List<ToolStripMenuItem> items)
{
items.ForEach(item =>
{
var dropdown = (ToolStripDropDownMenu)item.DropDown;
if (dropdown != null)
{
dropdown.ShowImageMargin = false;
SetValuesOnSubItems(item.DropDownItems.OfType<ToolStripMenuItem>().ToList());
}
});
}
This is a modified version of above. Use:
MainMenuStrip.HideImageMargins();
Because the recursive method performs the intended manipulation, I used overloading to make it clearer what is intended. Pattern matching is used because the above sample will throw an exception, not return null.
public static void HideImageMargins([NotNull] this MenuStrip menuStrip)
{
HideImageMargins(menuStrip.Items.OfType<ToolStripMenuItem>().ToList());
}
private static void HideImageMargins([NotNull] this List<ToolStripMenuItem> toolStripMenuItems)
{
toolStripMenuItems.ForEach(item =>
{
if (!(item.DropDown is ToolStripDropDownMenu dropdown))
{
return;
}
dropdown.ShowImageMargin = false;
HideImageMargins(item.DropDownItems.OfType<ToolStripMenuItem>().ToList());
});
}

Navigation Helper SaveState / LoadState works incorrect

First, I read a list of entries from a database and display it in a ListView. When I leave the page to show details of an entry, then go back to the list, everything is ok.
Next, I open another page to add one entry to the database.
Go back to the list, reading from database shows me the correct count.
When I go to display one detail, the correct count is stored in SaveState.
Go back to the list, LoadState give the wrong count. It's the former state.
Display other details and go back now works with the old list and do not show me the added entry.
This is my code:
private void getList()
{
memoList = new List<MemoItem>();
db.loadHistory(ref memoList);
DelButton.IsEnabled = false;
System.Diagnostics.Debug.WriteLine("GetList, memoList[{0}]", memoList.Count);
}
private void NavigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
if (memoList != null)
{
e.PageState["MemoItem"] = memoList;
if (memoSelected > -1)
e.PageState["memoSelected"] = memoSelected;
System.Diagnostics.Debug.WriteLine("SaveState, memoList[{0}]", memoList.Count);
}
}
private void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
if (e.PageState != null)
{
if (e.PageState.ContainsKey("MemoItem"))
{
memoList = (List<MemoItem>)e.PageState["MemoItem"];
System.Diagnostics.Debug.WriteLine("LoadState, memoList[{0}]", memoList.Count);
if (e.PageState.ContainsKey("memoSelected"))
memoSelected = (int)e.PageState["memoSelected"];
MemoListView.ItemsSource = memoList;
MemoListView.Visibility = Visibility.Visible;
}
else
{
getList();
showList();
}
}
}
Here are the Systems.Diagnostic outputs with comments in ():
GetList, memoList[8] (first time loaded)
SaveState, memoList[8] (leave the list to display details for one entry)
LoadState, memoList[8] (come back to the ListView)
SaveState, memoList[8] (leave the list to another page and add one entry)
GetList, memoList[9] (back to the list, read the correct entry count from database)
SaveState, memoList[9] (leave the ListView to display details for one entry)
LoadState, memoList[8] (come back to the ListView loads the wrong old list)
SaveState, memoList[8] (and works with the old list...)
LoadState, memoList[8] (...)
Remark: I can't call GetList from database every time, because I have to preserve checkmarks in the list which are not contained in the database.
What is wrong in my code? How can I resolve this problem? How to invalidate the StateEvent data after availability of a new list from database?
The problem is solved now. It was actually a consequence of the back stack manipulation.
I have four basic pages in my program, for which a GoBack makes sense. But I do not want to go back deeper. (I do not think anyone wants to go back 20 or more steps, but the seemingly unlimited BackStack would allow that.The required memory consumption is not negligible.)
In order to clear the BackStack at selecting one of the basic pages, I had this:
private void NavButton_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
if (b != null && b.Tag != null)
{
Type pageType = Type.GetType(b.Tag.ToString());
if (pageType != null)
{
if (rootFrame.CurrentSourcePageType != pageType)
{
rootFrame.Navigate(pageType, rootFrame);
// No goBack for Basic Pages
if (testBasicPage(pageType))
{
while (rootFrame.BackStackDepth > 1)
{
rootFrame.BackStack.RemoveAt(0);
}
}
}
}
}
}
That worked fine until I came across the problem with SaveState / Load State.
Now I first empty the BackStack and then navigate to the target page. That works.
private void NavButton_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
if (b != null && b.Tag != null)
{
Type pageType = Type.GetType(b.Tag.ToString());
if (pageType != null)
{
if (rootFrame.CurrentSourcePageType != pageType)
{
// No goBack for Basic Pages
if (testBasicPage(pageType))
{
while (rootFrame.BackStackDepth > 0)
{
rootFrame.BackStack.RemoveAt(0);
}
}
rootFrame.Navigate(pageType, rootFrame);
}
}
}
}

How do I make it so that a label changes text according to a variable

I have a label and I want it to display either Player or Console depending on what the variable answer is.
private void playerLabel_Click(object sender, EventArgs e)
{
string playerDetail = "Player",
consoleDetail = "Console";
if (Class.Method.Variable == 1)
{
Show.playerDetail();
}
if else (Class.Method.Variable == 0)
{
Show.consoleDetail();
}
}`
I then want to make it so that the label shows the string instead if you get me. I know I am not doing this properly but I can't work out how exactly to do this.
private void playerLabel_Click(object sender, EventArgs e)
{
string labelText = playerLabel.Text;
if (Class.Method.Variable == 1)
{
labelText = "Player";
Show.playerDetail();
}
else if(Class.Method.Variable == 0)
{
labelText = "Console";
Show.consoleDetail();
}
playerLabel.Text = labelText;
}
It would be better if your methods in Show class returned the appropriate string, so that you can do: playerLabel.Text = Show.WhateverDetail();. Additionally its even better if you could tie the Show method with the Variable value so that you don't have to use an if-else logic at all.
a. The Text property of the Label is what you want to set your strings to.
playerLabel.Text = playerDetail;
playerLabel.Text = consoleDetail;
b. Your if/else method should be in the form of:
if (test)
{
}
else if
{
}
else
{
}
You don't need the else if bit in the middle if there are only two branches.
c. I'm not sure about Show.consoleDetail() and Show.playerDetail(). Are 'consoleDetail()' and 'playerDetail()' method calls?

Google Suggestish text box (autocomplete)

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.

Categories

Resources