Loop though Children elements , Find something and access its sibling property - c#

I have a WrapPanel which contains some Images (thumbnails).
When the user press Left or Right arrow key, I want to show the next/previous image.
private void frmMain_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Right)
{
int j = 0;
foreach (Image child in WrapPanelPictures.Children)
{
if (child.Source == MainPic.Source)
{
MainPic.Source = WrapPanelPictures.Children[j + 1].Source;
}
j++;
}
}
}
Also I tried a LINQ approach, but I'm a beginner with LINQ.
var imgfound = from r in WrapPanelPictures.Children.OfType<Image>()
where r.Source == MainPic.Source
select r;
MessageBox.Show(imgfound.Source.ToString());
imgfound is supposed to be a list, right? maybe that's why I can't access Source property. Anyway this return the current Image shown. I want the sibling.
UPDATE:
Well I made a workaround as for now. But still waiting for a proper solution.
I created a ListBox and added all WrapPanel Childrens to it. Then used the SelectedItem and SelectedIndex properties to select the previous and next items.

The WrapPanelPictures.Children[j + 1].Source cannot work since you are trying to access the Source property of UIElement which does not exist. You should cast the UIElement to Image before accessing the Source:
MainPic.Source = (WrapPanelPictures.Children[j + 1] as Image).Source;
I am sure there are more elegant solutions to obtain the same results.

You got your answer why you can't access the Source property by Novitchi. I would nevertheless like to suggest that you rethink your code.
The way I see it, you are in control of what your wrap panel displays. That means you should be able to store things like "all the images" and also the selected index in a field or property. Instead of parsing your wrap panel's children for images every time around and comparing image sources, you should know at any given time what your selected image or index is.
The code might then roughly look something like this:
private List<BitmapImage> _images;
private int _selectedIndex;
private void frmMain_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Right)
{
_selectedIndex = (_selectedIndex + 1) % _images.Count;
MainPic.Source = _images[_selectedIndex];
}
}
If your UI is highly dynamic (drag/dropping images into the wrap panel or something like that), using Bindings to link your data with the UI is the way to go. In any case, I also strongly recommend considering a ViewModel pattern like MVVM.

use method FindVisualChildren. It traverses through Visual Tree and find your desired control.
This should do the trick
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
then you enumerate over the controls like so
foreach (TextBlock tb in FindVisualChildren<TextBlock>(window))
{
// do something with tb here
}

Do not declare j inside the foreach loop. Otherwise this will always show the image for j=0 which is WrapPanelPictures.Children[1]
private void frmMain_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Right)
{
int j = 0;
foreach (Image child in WrapPanelPictures.Children)
{
// int j = 0;
if (child.Source == MainPic.Source)
{
MainPic.Source = WrapPanelPictures.Children[j + 1].Source;
break;
}
j++;
}
}
}

Related

a way to access "code generated" labels in C# winforms [duplicate]

I have a ToolStripMenuItem called myMenu. How can I access this like so:
/* Normally, I would do: */
this.myMenu... etc.
/* But how do I access it like this: */
String name = myMenu;
this.name...
This is because I am dynamically generating ToolStripMenuItems from an XML file and need to reference MenuItems by their dynamically generated names.
Use the Control.ControlCollection.Find method.
Try this:
this.Controls.Find()
string name = "the_name_you_know";
Control ctn = this.Controls[name];
ctn.Text = "Example...";
Assuming you have the menuStrip object and the menu is only one level deep, use:
ToolStripMenuItem item = menuStrip.Items
.OfType<ToolStripMenuItem>()
.SelectMany(it => it.DropDownItems.OfType<ToolStripMenuItem>())
.SingleOrDefault(n => n.Name == "MyMenu");
For deeper menu levels add more SelectMany operators in the statement.
if you want to search all menu items in the strip then use
ToolStripMenuItem item = menuStrip.Items
.Find("MyMenu",true)
.OfType<ToolStripMenuItem>()
.Single();
However, make sure each menu has a different name to avoid exception thrown by key duplicates.
To avoid exceptions you could use FirstOrDefault instead of SingleOrDefault / Single, or just return a sequence if you might have Name duplicates.
Control GetControlByName(string Name)
{
foreach(Control c in this.Controls)
if(c.Name == Name)
return c;
return null;
}
Disregard this, I reinvent wheels.
Using the same approach of Philip Wallace, we can do like this:
public Control GetControlByName(Control ParentCntl, string NameToSearch)
{
if (ParentCntl.Name == NameToSearch)
return ParentCntl;
foreach (Control ChildCntl in ParentCntl.Controls)
{
Control ResultCntl = GetControlByName(ChildCntl, NameToSearch);
if (ResultCntl != null)
return ResultCntl;
}
return null;
}
Example:
public void doSomething()
{
TextBox myTextBox = (TextBox) this.GetControlByName(this, "mytextboxname");
myTextBox.Text = "Hello!";
}
I hope it help! :)
this.Controls.Find(name, searchAllChildren) doesn't find ToolStripItem because ToolStripItem is not a Control
using SWF = System.Windows.Forms;
using NUF = NUnit.Framework;
namespace workshop.findControlTest {
[NUF.TestFixture]
public class FormTest {
[NUF.Test]public void Find_menu() {
// == prepare ==
var fileTool = new SWF.ToolStripMenuItem();
fileTool.Name = "fileTool";
fileTool.Text = "File";
var menuStrip = new SWF.MenuStrip();
menuStrip.Items.Add(fileTool);
var form = new SWF.Form();
form.Controls.Add(menuStrip);
// == execute ==
var ctrl = form.Controls.Find("fileTool", true);
// == not found! ==
NUF.Assert.That(ctrl.Length, NUF.Is.EqualTo(0));
}
}
}
One of the best way is a single row of code like this:
In this example we search all PictureBox by name in a form
PictureBox[] picSample =
(PictureBox)this.Controls.Find(PIC_SAMPLE_NAME, true);
Most important is the second paramenter of find.
if you are certain that the control name exists you can directly use it:
PictureBox picSample =
(PictureBox)this.Controls.Find(PIC_SAMPLE_NAME, true)[0];
You can use find function in your Form class. If you want to cast (Label) ,(TextView) ... etc, in this way you can use special features of objects. It will be return Label object.
(Label)this.Controls.Find(name,true)[0];
name: item name of searched item in the form
true: Search all Children boolean value
this.Controls["name"];
This is the actual code that is ran:
public virtual Control this[string key]
{
get
{
if (!string.IsNullOrEmpty(key))
{
int index = this.IndexOfKey(key);
if (this.IsValidIndex(index))
{
return this[index];
}
}
return null;
}
}
vs:
public Control[] Find(string key, bool searchAllChildren)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key", SR.GetString("FindKeyMayNotBeEmptyOrNull"));
}
ArrayList list = this.FindInternal(key, searchAllChildren, this, new ArrayList());
Control[] array = new Control[list.Count];
list.CopyTo(array, 0);
return array;
}
private ArrayList FindInternal(string key, bool searchAllChildren, Control.ControlCollection controlsToLookIn, ArrayList foundControls)
{
if ((controlsToLookIn == null) || (foundControls == null))
{
return null;
}
try
{
for (int i = 0; i < controlsToLookIn.Count; i++)
{
if ((controlsToLookIn[i] != null) && WindowsFormsUtils.SafeCompareStrings(controlsToLookIn[i].Name, key, true))
{
foundControls.Add(controlsToLookIn[i]);
}
}
if (!searchAllChildren)
{
return foundControls;
}
for (int j = 0; j < controlsToLookIn.Count; j++)
{
if (((controlsToLookIn[j] != null) && (controlsToLookIn[j].Controls != null)) && (controlsToLookIn[j].Controls.Count > 0))
{
foundControls = this.FindInternal(key, searchAllChildren, controlsToLookIn[j].Controls, foundControls);
}
}
}
catch (Exception exception)
{
if (ClientUtils.IsSecurityOrCriticalException(exception))
{
throw;
}
}
return foundControls;
}
Assuming you have Windows.Form Form1 as the parent form which owns the menu you've created. One of the form's attributes is named .Menu. If the menu was created programmatically, it should be the same, and it would be recognized as a menu and placed in the Menu attribute of the Form.
In this case, I had a main menu called File. A sub menu, called a MenuItem under File contained the tag Open and was named menu_File_Open. The following worked. Assuming you
// So you don't have to fully reference the objects.
using System.Windows.Forms;
// More stuff before the real code line, but irrelevant to this discussion.
MenuItem my_menuItem = (MenuItem)Form1.Menu.MenuItems["menu_File_Open"];
// Now you can do what you like with my_menuItem;
Since you're generating them dynamically, keep a map between a string and the menu item, that will allow fast retrieval.
// in class scope
private readonly Dictionary<string, ToolStripMenuItem> _menuItemsByName = new Dictionary<string, ToolStripMenuItem>();
// in your method creating items
ToolStripMenuItem createdItem = ...
_menuItemsByName.Add("<name here>", createdItem);
// to access it
ToolStripMenuItem menuItem = _menuItemsByName["<name here>"];
Have a look at the ToolStrip.Items collection. It even has a find method available.
You can do the following:
private ToolStripMenuItem getToolStripMenuItemByName(string nameParam)
{
foreach (Control ctn in this.Controls)
{
if (ctn is ToolStripMenuItem)
{
if (ctn.Name = nameParam)
{
return ctn;
}
}
}
return null;
}
A simple solution would be to iterate through the Controls list in a foreach loop. Something like this:
foreach (Control child in Controls)
{
// Code that executes for each control.
}
So now you have your iterator, child, which is of type Control. Now do what you will with that, personally I found this in a project I did a while ago in which it added an event for this control, like this:
child.MouseDown += new MouseEventHandler(dragDown);

Capture RichTextBlock Hyperlink events

I'm trying to capture the click event of Hyperlinks inside a dynamically generated RichTextBlock.
I'm dynamically generating the contents of a richtextblock and then applying them with XamlReader. The content can vary quite a bit, so I can't manually parse the xaml and hook up events at that point.
My basic idea is to, once the richtextblock is loaded, find all Hyperlinks in it and hook up their click event there. This is my current code:
public class HookUpEvents()
{
foreach (var child in FindVisualChildren<Hyperlink>(richtxtblock))
{
child.Click += MyFunction;
}
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
Obviously, it isn't working. It looks like the FindVisualChildren function isn't returning any Hyperlinks. Any ideas on how I can achieve this?
Well, I'm sure late to the party, but RichTextBlock won't place his Blocks and their Inlines in VisualTree most of the time. To find all Inline-based elements (Run,Span,Bold etc.) you will need to loop through all content, by visiting each Block and subsequent Inline's. I would suggest something like this:
public static IEnumerable<T> GetAllTextElements<T>(this RichTextBlock rtb) where T : TextElement
{
var result = new List<T>();
var blocks = rtb.Blocks;
foreach (var block in blocks)
{
if (block is T)
{
result.Add(block as T);
continue;
}
var inlines = ((Paragraph)block).Inlines;
var res = TraverseInline<T>(inlines);
if (res != null && res.Any())
result.AddRange(res);
}
return result;
}
private static IEnumerable<T> TraverseInline<T>(IEnumerable<Inline> inlines) where T : TextElement
{
var result = new List<T>();
foreach (var item in inlines)
{
if (item is T)
{
result.Add(item as T);
continue;
}
else if (item is Span) // first Inline derived class to have own `Inlines`
{
var spanItem = item as Span;
var spanInlines = spanItem.Inlines;
var results = TraverseInline<T>(spanInlines);
if (results != null && results.Any())
result.AddRange(results);
}
}
return result;
}
So you can look for any TextElement-derived item with it.
Usage would be something like:
var textHyperlinks = myRichTextBlock.GetAllTextElements<Hyperlink>();
This will do as far as you don't use InlineUIContainer. That type of Inline behaves differently, as you can put anything UIElement-based as it's Child property. In that case your initial approach should work.
There's a couple of things here:
If you're trying to find the hyperlink inside of the RichTextBlock, its type is: Windows.UI.Xaml.Documents.Hyperlink. Not the type of the HyperLinkButton.
You can put the Click event handler in your text and then provide the handler method in your code behind file. If you dynamically generate text that looks like:
<Paragraph>
Text with a
<Hyperlink x:Name="link" Click="link_Click">link.</Hyperlink>
</Paragraph>
Feed that to the XamlReader, and put the following code in your code behind file:
private void link_Click(Windows.UI.Xaml.Documents.Hyperlink sender, Windows.UI.Xaml.Documents.HyperlinkClickEventArgs args)
{
Debug.WriteLine("Handle link click, by: " + sender.Name);
}
Then it should connect up correctly at runtime. And you can do whatever you want on the Click event handler. Even if there are multiple links, you can name them differently and just use one click handler to process.

Find all TextBox controls in UWP Page

I need to find all TextBox(es) that are on a UWP Page but having no luck. I thought it would be a simple foreach on Page.Controls but this does not exist.
Using DEBUG I am able to see, for example, a Grid. But I have to first cast the Page.Content to Grid before I can see the Children collection. I do not want to do this as it may not be a Grid at the root of the page.
Thank you in advance.
UPDATE: This is not the same as 'Find all controls in WPF Window by type'. That is WPF. This is UWP. They are different.
You're almost there! Cast the Page.Content to UIElementCollection, that way you can get the Children collection and be generic.
You'll have to make your method recurse and look either for Content property if element is a UIElement or Children if element is UIElementCollection.
Here's an example:
void FindTextBoxex(object uiElement, IList<TextBox> foundOnes)
{
if (uiElement is TextBox)
{
foundOnes.Add((TextBox)uiElement);
}
else if (uiElement is Panel)
{
var uiElementAsCollection = (Panel)uiElement;
foreach (var element in uiElementAsCollection.Children)
{
FindTextBoxex(element, foundOnes);
}
}
else if (uiElement is UserControl)
{
var uiElementAsUserControl = (UserControl)uiElement;
FindTextBoxex(uiElementAsUserControl.Content, foundOnes);
}
else if (uiElement is ContentControl)
{
var uiElementAsContentControl = (ContentControl)uiElement;
FindTextBoxex(uiElementAsContentControl.Content, foundOnes);
}
else if (uiElement is Decorator)
{
var uiElementAsBorder = (Decorator)uiElement;
FindTextBoxex(uiElementAsBorder.Child, foundOnes);
}
}
Then you call that method with:
var tb = new List<TextBox>();
FindTextBoxex(this, tb);
// now you got your textboxes in tb!
You can also use the following generic method from the VisualTreeHelper documentation to get all your child controls of a given type:
internal static void FindChildren<T>(List<T> results, DependencyObject startNode)
where T : DependencyObject
{
int count = VisualTreeHelper.GetChildrenCount(startNode);
for (int i = 0; i < count; i++)
{
DependencyObject current = VisualTreeHelper.GetChild(startNode, i);
if ((current.GetType()).Equals(typeof(T)) || (current.GetType().GetTypeInfo().IsSubclassOf(typeof(T))))
{
T asType = (T)current;
results.Add(asType);
}
FindChildren<T>(results, current);
}
}
It basically recursively get the children for the current item and add any item matching the requested type to the provided list.
Then, you just have to do the following somewhere to get your elements:
var allTextBoxes = new List<TextBox>();
FindChildren(allTextBoxes, this);
To my mind, you could do it in the same way as in WPF. Because UWP uses mostly the same XAML that WPF.
So, please check out answer for the same question about WPF

Get the index of Item selected in ListView

I've been searching for about an hour already and couldn't find a best solution.
I am migrating from VB.NET to C# Forms and to C# WPF.
Never mind that...
so I use this code for C# forms and it works, but not in C# WPF
if (ListView1.SelectedItems.Count > 0)
{
for (lcount = 0; lcount <= ListView1.Items.Count - 1; lcount++)
{
if (ListView1.Items[lcount].Selected == true)
{
var2 = lcount;
break;
}
}
}
this is the way I want to get the index of the item clicked in listbox.
I have the error in .SELECTED
please help.
You can get SelectedIndex from listView. No need to traverse over all items because as per your code you seems to be interested in index of any selected item.
var2 = ListView1.SelectedIndex;
OR
simply this will work if interested in only first index:
if (lst.SelectedItems.Count > 0)
{
var2 = lst.Items.IndexOf(lst.SelectedItems[0]);
}
If you are using the .NET Compact Framework, SelectedIndex is not supported. For a general solution, I prefer SelectedIndices:
ListView.SelectedIndexCollection indices = lst.SelectedIndices;
if (indices.Count > 0)
{
// Do something with indices[0]
}
For Visual Studio 2015, SelectedIndex does not seem to be available. Instead, you can use SelectedIndices[x] where x=0 will give you the first selected item:
listView.SelectedIndices[0]
You can also set the MultipleSelect property to false to only allow one item to be selected at a time.
It can return NULL. Also the SelectedIndexChanged event can be FIRED TWICE. And the first time, there nothing selected yet.
So the only safe way to find it is like this:
private void lv1_SelectedIndexChanged(object sender, EventArgs e)
{
if (lv1.FocusedItem == null) return;
int p = lv1.FocusedItem.Index;
... now int p has the correct value...
sColl.Clear();
string item = String.Empty;
if (listView1.SelectedItems.Count > 0) {
for (int i = 0; i < listView1.SelectedItems.Count; i++) {
if (listView1.SelectedItems[i].Selected) {
int i2 = listView1.SelectedItems[i].Index;
item = listView1.Items[i2].Text;
sColl.Add(item);
}
}
}
listView1.SelectedItems.Clear();
foreach (var itemS in sColl)
{
string items = itemS;
}
sColl.Clear();
listView1.SelectedItems.Clear();
Why don't bring back the SelectedIndex ? Add this extension after your current namespace.
public static class Extension
{
public static int SelectedIndex(this ListView listView)
{
if (listView.SelectedIndices.Count > 0)
return listView.SelectedIndices[0];
else
return 0;
}
}
Encapsulate this class in a namespace called Extensions and then add this inside your projects namespace to use the extension.
using Extensions;
Then simply use like this
private void ListView1_SelectedIndexChanged(object sender, EventArgs e)
{
int selectionindex = ListView1.SelectedIndex();
ListViewItem seletedItem = ListView1.Items[selectionindex];
}
P.S.
The extension method should have returned -1 on Else, but as long as you're using it from the SelectedIndexChanged event, you're fine as it won't get fired if there are no items.
This is by design, as the SelectedIndexChanged event gets fired twice. Once to deselect the initial item, then to select the new one.
Proper way is to return -1 and check for negative numer.
This is also why someone here got and ArgumentOutOfRange Exception.

Why ItemContainerGenerator.ContainerFromIndex() returns null and how to avoid this behavior?

I'm using this snippet to analyze the rows I've selected on a datagrid.
for (int i = 0; i < dgDetalle.Items.Count; i++)
{
DataGridRow row = (DataGridRow)dgDetalle.ItemContainerGenerator.ContainerFromIndex(i);
FrameworkElement cellContent = dgDetalle.Columns[0].GetCellContent(row);
// ... code ...
}
The cycle runs smoothly, but when processing certain indexes, the second line throws a null exception. MSDN's documentation says that ItemContainerGenerator.ContainerFromIndex(i) will return null if 'if the item is not realized', but this doesn't help me to guess how could I get the desired value.
How can I scan all the rows? Is there any other way?
UPDATE
I'm using this snippet to read a CheckBox as explained here. So I can't use binding or ItemSource at all unless I change a lot of things. And I cannot. I'm doing code maintenance.
Try this,
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
grid.UpdateLayout();
grid.ScrollIntoView(grid.Items[index]);
row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
}
The DataGrid is virtualizing the items, the respective rows (i.e. containers) are only created when the row is in view.
You could either turn off virtualization (which makes the first time loading very slow if you have many items, also the memory usage will be higher) or you just iterate over the data and check the values of the data objects' properties which should be bound to the data-grid. Usually you should not need the UI elements at all...
Use this subscription:
TheListBox.ItemContainerGenerator.StatusChanged += (sender, e) =>
{
TheListBox.Dispatcher.Invoke(() =>
{
var TheOne = TheListBox.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem;
if (TheOne != null)
// Use The One
});
};
In addition to other answers: items aren't available in constructor of the control class (page / window / etc).
If you want to access them after created, use Loaded event:
public partial class MyUserControl : UserControl
{
public MyUserControl(int[] values)
{
InitializeComponent();
this.MyItemsControl.ItemsSource = values;
Loaded += (s, e) =>
{
for (int i = 0; i < this.MyItemsControl.Items.Count; ++i)
{
// this.MyItemsControl.ItemContainerGenerator.ContainerFromIndex(i)
}
};
}
}
In my case grid.UpdateLayout(); didn't help an I needed a DoEvents() instead:
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
WPFTools.DoEvents();
grid.ScrollIntoView(grid.Items[index]);
row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
}
/// <summary>
/// WPF DoEvents
/// Source: https://stackoverflow.com/a/11899439/1574221
/// </summary>
public static void DoEvents()
{
var frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(
delegate (object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}), frame);
Dispatcher.PushFrame(frame);
}

Categories

Resources