targeting dynamic items within a stackpanel - c#

I'm a little new to this sort of coding, but i am trying to access dynamically created TextBlock properties (like, TextBlock.Tag, Name, etc) within a StackPanel every tick of a timer. What i intend to do with each TextBlock is to see what its tag property is, and if it matches a conditoinal, for the timer to alter the TextBlock property in some way.
So it's a matter of finding a way to code every timer Tick: "For every TextBlock.Tag in StackPanel, if TextBlock.Tag == this, do this to the TextBlock."
Here is some code to help visualize what I am doing:
Xaml code:
<StackPanel Name="StackP" Margin="6,0,6,0"/>
C# code:
{
for (var i = 0; i < MaxCountOfResults; ++i)
{
TextBlock SingleResult= new TextBlock { Text = Resultname.ToString(), FontSize = 20, Margin = new Thickness(30, -39, 0, 0) };
//a condition to alter certain TextBlock properties.
if (i == .... (irrelevant to this example))
{
SingleResult.Foreground = new SolidColorBrush(Colors.Yellow);
SingleResult.Tag = "00001";
}
//Add this dynamic TextBlock to the StackPanel StackP
StackP.Children.Add(SingleResult);
}
//the timer that starts when this entire function of adding the TextBlocks to the StackPanel StackP tree is done.
Atimer = new Timer(new TimerCallback(Atimer_tick), 0, 0, 100);
}
public void Atimer_tick(object state)
{
The area where I have no idea how to reference the Children of stackpanel StackP with every timer tick. I need help :(
}
Thank you guys. I am still learning this and the help is needed.

you should be able to use timer, but I'd recommend using BackgroundWorker to perform a loop instead of timer events, which could collide. Even better - use SilverLight-style animations with triggers.
On the non-UI thread you'd want to use Dispatcher call to invoke your async code back on the UI thread, something like:
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
try
{
foreach (TextBlock txb in StackP.Children){
txb.Text = "xyz";
}
}
catch (Exception ex)
{
Debug.WriteLine("error: "+ex);
}
});

Related

C# WPF dynamic names for input

So I'm trying to add dynamically inputs and retreive date from them and only do an action when a user presses enter in the input. So, what I'm currently doing is appending the inputs to a stacklayout. which works fine. Also the naming works. I use the following function;
private void GenerateGTKInputs()
{
// Based on the settings for the tour
// we generate the correct inputs in the stacklayout given in the XAML
// First: clear all the children
stackpanel_gtk.Children.Clear();
if (inp_team_number.Text != "")
{
// get the data for the part and the class etc...
var data_gtk = tour_settings[(Convert.ToInt32(inp_team_number.Text.Substring(0, 1)) - 1)].tour_data[inp_tour_part.SelectedIndex].gtks;
// Now: Make the layout
foreach (var item in data_gtk)
{
// Stack panel (main 'div')
StackPanel main_stack_panel = new StackPanel()
{
Orientation = Orientation.Horizontal,
HorizontalAlignment = HorizontalAlignment.Left
};
// Text blok with the name of the GTK
TextBlock gtk_name = new TextBlock()
{
FontWeight = FontWeights.Bold,
Text = "GTK " + item.gtk
};
// Input field
Xceed.Wpf.Toolkit.MaskedTextBox input = new Xceed.Wpf.Toolkit.MaskedTextBox()
{
Margin = new Thickness(15, 0, 0, 0),
Width = 40,
Height = Double.NaN, // Automatic height
TextAlignment = TextAlignment.Center,
Mask = "00:00",
Name = "gtk_" + item.gtk
};
// Add to the main stack panel
main_stack_panel.Children.Add(gtk_name);
main_stack_panel.Children.Add(input);
// Append to the main main frame
stackpanel_gtk.Children.Add(main_stack_panel);
}
}
}
Now as you can see, I'm giving them a name, but I have no clue what so ever on how to "bind" an trigger event (KeyDown) with a check on enter button press with dynamic names. Could anyone help me out here?
You "bind" a trigger event by adding to the appropriate event of the control - in this case you need to create a method like :
private void OnKeyDown(object sender, System.Windows.Input.KeyEventArgs keyEventArgs)
{
// Get reference to the input control that fired the event
Xceed.Wpf.Toolkit.MaskedTextBox input = (Xceed.Wpf.Toolkit.MaskedTextBox)sender;
// input.Name can now be used
}
and add this to the KeyDown event :
input.KeyDown += OnKeyDown;
You can chain as many event handlers as you want by adding further handlers in this fashion.
This can be done at any time after you create the control. To "unbind" the event you "subtract" it from the event :
input.KeyDown -= OnKeyDown;

When one label animates, the second label automatically animates

As per the title, I DO not want that to happen.
I have 2 separate labels and I want to make them flash provided an if statement is met. But whenever either label animates, the second label automatically follows to animate. I tried to test it by just changing the foreground color of the label and it works. The problem seems to occur only when I use the animation. Can anyone please help to spot my problem please. Tnks.
void AlertAnimation(Label label)
{
label.Foreground.BeginAnimation(SolidColorBrush.ColorProperty,
new ColorAnimation
{
To = Colors.Red,
Duration = TimeSpan.FromSeconds(0.1),
AutoReverse = true,
RepeatBehavior = new RepeatBehavior(3)
});
}
void calc()
{
if (maxValue > slider1.Value)
{
AlertAnimation(label10);
//label10.Foreground = Brushes.Red; //it works when I use this line.
}
else if (minValue < slider2.Value)
{
AlertAnimation(label11);
//label11.Foreground = Brushes.Red;
}
}

Wpf application and Threads

I have problem with my GUI and Threads.
The GUI contains DataGrid. Every X time the program do some query and getting a list of items that I want to fill into the DataGrid.
So far so good:
private void loadTaskList() //Call every X time
{
List<myObject> myList = myquery();
this.Dispatcher.Invoke((Action)(() =>
{
TaskListTable.Items.Clear(); //Clear the DataGrid
foreach (myObject O in myList) //Add the items from the new query.
{
TaskListTable.Items.Add(O);
}
}));
FindSelectionObject(); // <-- see next explanation.
}
When the user click on one of the objects in the datagrid, the line color changed (it works fine), but when the program reload the table,The painted line disappears (Becuse I clear and add new objects).
To deal with it, I created the function FindSelectionObject():
private void FindSelectionObject()
{
this.Dispatcher.Invoke((Action)(() =>
{
this.SelectedIndex = TaskListTable.Items.IndexOf((myObject)lastSelectionObject); //find index of the new object that equels to the last selection object.
var row = TaskListTable.ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as DataGridRow; //get the row with the index
row.Background = Brushes.LightGoldenrodYellow; //repaint
}));
}
The problem: Everything works fine, but sometimes when the program reloads, the line flashes per second and then highlighted back, and sometimes it's not painting it at all (untill the next reload).
I can't understand why this is happening. I think maybe the FindSelectionObject() begins to run before the loadTaskList() ends to invoke all and add the new objects into the datagrid.
But if so - Why? And how can I fix it?
In the bottom line, I want that after every reload the line re-paint immediately..
Thanks for any advice!
A few things to think about:
You should keep in mind that the DataGrid uses virtualization, which means that each item in your items source does not get its very own UI element. The UI elements are created to fill the visible area, and then re-used depending on which data-source item is currently bound to each one (this changes when you scroll for instance or change the items source). This may cause you problems in the future if you use your current approach, so keep this in mind.
The other thing is that the DataGrid may require more "cycles" of the layout process in order to update its UI. You may simply be calling FindSelectionObject prematurely. You have queued FindSelectionObject right after the invocation in loadTaskList. If the DataGrid needs to perform some actions which are queued on the dispatcher after the items source has changed, these will execute after the invocation in FindSelectionObject.
Try this instead:
private void loadTaskList() //Call every X time
{
List<myObject> myList = myquery();
this.Dispatcher.Invoke((Action)(() =>
{
TaskListTable.Items.Clear(); //Clear the DataGrid
foreach (myObject O in myList) //Add the items from the new query.
{
TaskListTable.Items.Add(O);
}
// The items of the grid have changed, NOW we QUEUE the FindSelectionObject
// operation on the dispatcher.
FindSelectionObject(); // <-- (( MOVE IT HERE )) !!
}));
}
EDIT: OK, so if this fails then maybe this will cover the case in which the above solution fails: subscribe to the LoadingRow event of DataGrid and set the appropriate background color if the row is the selected one. So in the cases when new rows are created this event will be called (due to virtualization it is not called per item in items source, but per actual row UI element). In the event args you will have access to the created DataGridRow instance.
I think this issue could be a visual thread synchronization. For this you can create and use a method similar like this:
public void LockAndDoInBackground(Action action, string text, Action beforeVisualAction = null, Action afterVisualAction = null)
{
var currentSyncContext = SynchronizationContext.Current;
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (_, __) =>
{
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
currentSyncContext.Send((t) =>
{
IsBusy = true;
BusyText = string.IsNullOrEmpty(text) ? "Espere por favor..." : text;
if (beforeVisualAction != null)
beforeVisualAction();
}, null);
action();
currentSyncContext.Send((t) =>
{
IsBusy = false;
BusyText = "";
if (afterVisualAction != null)
afterVisualAction();
}, null);
};
backgroundWorker.RunWorkerAsync();
}
IsBusy and BusyText are particular properties, that you can remove. The action variable will be the action to do in background (load your items for instance). beforeVisualAction and afterVisualAction are the visual actions you may want to do before and after the background action. Here are any visual update, for instance select your item, change color, set a view model variable that raise a binding update,... (any action that update the view).
Hope this method helps.
Are you maintaining the reference to lastSelectionObject somewhere? You say you're adding new objects, if they are truly new then the reference will be different and the reference comparison happening in IndexOf will not find it.

What is the best way to create a Numbered RichtextBox?

The goal is to add a column of paragraph numbers to a RichTextBox (the numbers show the index of paragraphs in that richtextbox.Document.Blocks). Currently, I use this block of code in LayoutUpdated event of the RichTextBox:
bool _added=false
void onLayoutUpdated(object sender, EventArgs e)
{
if (!_added)
{
_added= true;
scv = Helper.GetFind.FindChild<ScrollViewer>(this, null);
if (scv != null)
{
FrameworkElement documentView = scv.Content as FrameworkElement;
scv.ClearValue(ScrollViewer.ContentProperty);
Grid grid= new Grid();
... I will talk about what I have added here...
scv.Content = grid;
UpdateLayout();
}
}
}
In the grid, I add two columns, the first one is a StackPanel and the second one is the documentView. For each paragraph I add a TextBlock to the StackPanel.Children and I set the height of each textBlock by using Paragraph.ElementStart.GetCharacterRect(LogicalDirection.Forward) methods and the Top & Bottom Properties of the returned Rect(s).
Everything is fine and when there are less than 500 paragraphs, the numbering updates quickly, But as the text gets larger, it gets slower. How can I make it more efficient? Should I use a Canvas instead of a StackPanel? Or is there any better way of doing this?
Thanks.
ListView GridView. Support virtualiztion. I use textblock for each line in some very large document and works great.
I used the procedure I mensioned in the question and then Dispacher.BeginInvoke(...) method. I set DispatcherPriority to ApplicationIdle. I call it when Width chaneges or new Paragraph is added. Sth like this:
_updateIfWidthChangedDispacherO= _ownerTextBox.Dispatcher.BeginInvoke((Action)(() =>
{
updateIfWidthChanged();
}),
DispatcherPriority.ApplicationIdle);

Dynamic and Immutable UIElement Arrays

I have a WrapPanel that contains multiple Canvas of the same size. Each Canvas has some UIElements (i.e. TextBox, TextBlock, Buttons etc) as children. The creation of each Canvas (including its UIElement children) and the number of Canvas to be created are all done in run-time code behind (no XAML).
Initially I did the following, which works:
// declare as class properties, so all function can access them
WrapPanel wp = new WrapPanel();
Canvas[] cv = new Canvas[500];
TextBox[] tb = new TextBox[500];
// A function (e.g. a Button_Click event) that generates multiple Canvas in a WrapPanel
for (int i = 0; i<myInt; i++)
{
cv[i] = new Canvas();
tb[i] = new TextBox();
cv[i].Children.Add(tb[i]);
wp.Children.Add(cv[i]);
}
The above code is straight forwards works OK - Until I implement add, minus and destroy buttons where I could
1. Add an additional `Canvas` on a click event
2. Remove the last `Canvas` on a click event
3. Destroy a specific `Canvas` in the `WrapPanel` on a click event (may ba a little cross icon in each `Canvas`)
If I process some combination of the above 3 actions, I could easily create UIElements of the same index or create Canvas that goes out of the range of what it had been declared initially.
I looked into List however, each Canvas have different properties (each also has UIElement Children with different properties) and I can't figure out how List would solve it. A way for me to go around that is to declare a super large Array size for Canvas (e.g. Canvas[] cv = new Canvas[99999] but I though that's not very efficient.
Also, if I use List, how could I change properties of a specific UIElement after the they are generated? E.g. If i add 10 Canvas and add to List, and after they are all generated, I need to select the 5th Canvas and change a TextBox.Text, how do I access it like I did in an Array (i.e. tb[5].Text = "Hello")?
Can anyone show me some approaches to this problem?
Just a direct translation on how to do this with a list instead below. Given your code I don't know why you want to keep track of the canvas and textbox'es in a list - you can just access the children collection of the WrapPanel directly instead - let's assume you do need these separate collections for now.
WrapPanel wp = new WrapPanel();
List<Canvas> cvList = new List<Canvas>();
List<TextBox> tbList = new List<TextBox>();
public void Init()
{
int myInt = 500;
// in a function (e.g. a Button_Click event) to generate the multiple Canvas in a WrapPanel
for (int i = 0; i < myInt; i++)
{
Canvas cv = new Canvas();
TextBox tb = new TextBox();
cv.Children.Add(tb);
wp.Children.Add(cv);
cvList.Add(cv);
tbList.Add(tb);
}
}
public void AddCanvas()
{
Canvas cv = new Canvas();
TextBox tb = new TextBox();
cv.Children.Add(tb);
wp.Children.Add(cv);
cvList.Add(cv);
tbList.Add(tb);
}
public void RemoveCanvas()
{
wp.Children.RemoveAt(wp.Children.Count-1);
cvList.RemoveAt(cvList.Count - 1);
tbList.RemoveAt(cvList.Count - 1);
}
Edit for added comment:
E.g. If i add 10 Canvas, and after
they are all generated, I need to
select the 5th Canvas and change a
TextBox.Text, how do I access it like
I did in an Array (i.e. tb[5].Text =
"Hello")?
You can just access the children directly. You know you only added Canvas elements to your WrapPanel. So you could do (wp is the WrapPanel again):
TextBox textbox = (wp.Children[5] as Canvas).Children[0] as TextBox;
textbox.Text = "Hello";
Just operate directly on the WrapPanel's Children collection.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AddCanvasToWrapPanel(this.TestWrapPanel);
RemoveLastCanvasFromWrapPanel(this.TestWrapPanel);
AddCanvasToWrapPanel(this.TestWrapPanel);
DestroyCanvasAtWrapPanelIndex(this.TestWrapPanel, 0);
}
private void AddCanvasToWrapPanel(WrapPanel wp)
{
TextBox t = new TextBox();
Canvas c = new Canvas();
c.Children.Add(t);
wp.Children.Add(c);
}
private void RemoveLastCanvasFromWrapPanel(WrapPanel wp)
{
wp.Children.RemoveAt(wp.Children.Count - 1);
}
private void DestroyCanvasAtWrapPanelIndex(WrapPanel wp, int index)
{
wp.Children.RemoveAt(index);
}
}
}

Categories

Resources