[https://drive.google.com/file/d/0B1xZLc69ZnLkWWRuaE9ycmhIXzg/view?usp=sharing]
This is the entire project - vs2013.
1You can see how the FindVisualChildren() works to assign every letter to a button's content, I need to do this for every word on the file, but it only works one time.
I have a text file, with 10 words (one per line). Once the word is read, it's placed into a text box, and spread, letter by letter, in order to be assigned to a button content in joint with some random letters to fill all the buttons. This step is made by using the FindVisualChildren() method.
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;
}
}
}
}
and
//Add random letters to all buttons
foreach (Button tb in FindVisualChildren<Button>(panel))
{
tb.Content = resultsChar[array[iconta]];
tb.Click += Button_Click;
++iconta;
}
Once all the letters are assigned, the user must create the word using every letter in every button, when the word is organized as corresponds to the one in the text box, then another word is called from the text, and the letters must be reassigned to the buttons with other random letters.
Everything works great, but just the first time. After that, I can't call the FindVisualChildren() method n times. This thing just work for the first word, and if I put this inside a while loop, just shows me the last word. not step by step.
Without a good Minimal, Complete, and Verifiable example that reliably reproduces your problem, it's impossible to know what exactly that problem is. From your problem description, it sounds like you might be running some code in the constructor, which should instead be placed elsewhere so that it can safely run multiple times.
That said, I do agree with the basic motivation (though not the presentation at all) of the comment that explains that you should not be messing directly with the visual tree at all.
In many APIs, and WPF especially, the "right way" to do things is to create a custom class — your "view model — which is a type that maintains the state of your program in a completely separate and independent way from the visual aspect of your program. In your code example, this class would keep track of the word and letters to be displayed, and any other state for the program's underlying logic, as well as any methods for controlling or modifying that state.
As an example, I wrote a class that could serve as the view model in your program:
class Model : INotifyPropertyChanged
{
private static readonly Random _random = new Random();
private const int _kcolumnCount = 6;
private const int _krowCount = 4;
private string _word;
public ObservableCollection<char> Letters { get; private set; }
public string Word
{
get { return _word; }
set { _UpdateValue(ref _word, value); }
}
public int ColumnCount { get { return _kcolumnCount; } }
public int RowCount { get { return _krowCount; } }
public Model()
{
Letters = new ObservableCollection<char>();
for (int i = 0; i < ColumnCount * RowCount; i++)
{
Letters.Add(' ');
}
}
public void UpdateLetters()
{
char[] characters = new char[ColumnCount * RowCount];
for (int i = 0; i < characters.Length; i++)
{
if (Word != null && i < Word.Length)
{
characters[i] = Word[i];
}
else
{
characters[i] = (char)('a' + _random.Next(26));
}
}
for (int i = characters.Length - 1; i > 0 ; i--)
{
int j = _random.Next(i + 1);
if (i != j)
{
char chT = characters[i];
characters[i] = characters[j];
characters[j] = chT;
}
}
for (int i = 0; i < characters.Length; i++)
{
Letters[i] = characters[i];
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void _UpdateValue<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!object.Equals(field, newValue))
{
field = newValue;
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
It has three key features:
It stores the current word.
It stores the current list of letters to be displayed.
It provides a method to cause the list of letters to be updated, based on the current word.
It also includes the values for the row and column counts of your grid.
You'll notice that this class is entirely comprehensible as the fundamental logic for your program, and yet has nothing in it that is directly related to the implementation of the UI.
Having written such a class, it is then very easy to create a simple XAML markup for the user interface:
<Window x:Class="TestSO36231872RandomLetters.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:TestSO36231872RandomLetters"
xmlns:s="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:Model/>
</Window.DataContext>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="3">
<TextBox Width="80" Text="{Binding Word}"/>
<Button HorizontalAlignment="Left" Content="Update Word"
Margin="3,0" Click="Button_Click"/>
</StackPanel>
<ItemsControl ItemsSource="{Binding Letters}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid IsItemsHost="True" Rows="{Binding RowCount}" Columns="{Binding ColumnCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type s:Char}">
<Button Content="{Binding}" FontSize="16"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Window>
The only thing left is the Click event handler (there are other ways to deal with user input, but this is the simplest and suits this particular example just fine). This goes in the code-behind for the Window class (i.e. "MainWindow.xaml.cs"):
private void Button_Click(object sender, RoutedEventArgs e)
{
Model model = (Model)DataContext;
model.UpdateLetters();
}
This causes the UpdateLetters() method to be called with the "Update Word" button is clicked.
Of course, this is not your full program. I don't really fully understand what you mean by "the user must create the word using every letter in every button". If you expect the user to click the buttons in the grid, you'll need to hook up another handler in the template for the buttons, and of course deal with the context somehow. But that's a "whole 'nother ball o' wax". I hope the above suffices to get you pointed back in the correct direction for dealing with WPF.
Related
I've been busy experimenting with UWP and WPF. After some getting used to, sometimes stupid, quirks, I decided to make one of my signature... "way overscoped" projects in WPF.
Anyway I'm making an application where I need to bind properties in a static class to UI elements (and when the properties change the UI elements need to change too). I know bindings exist but I've been trying for ages to get the UI to update when the property changes (with INotifyPropertyChanged and the PropertyChanged eventhandler). Eventually, i gave up and decided to make my own binding system(kinda anyway... I've got expansions planned, which is why i want it to be custom).
working of the code:
[Design Time]
Basically, what i have to do is make a property in the VNClient class, add a BindingAttribute(string bindingName) to it and set the Tag of the UI element i want to bind it to to the bindingName. I've got that setup.
[Runtime (only once at startup)]
Now the code will get all properties from the VNClient class with a BindingAttribute and add them to a dictionary as keys, then it will recursively look through the XAML hierarchy and any element with a tag that is also in the dictionary (meaning its bindable) will be added as a value to the dictionary.
[Runtime (every time a property changes)]
An event is fired telling the BindingManager which property changed. It will then get that property name from the dictionary (along with a dependency property but that's not implemented yet) to see which UI elements are bound to that property, then it will change the correct property to the correct value.
Here is the BindingManager:
internal class BindingManager
{
Dictionary<string, List<FrameworkElement>> staticReferenceBindings = new();
public BindingManager()
{
VNClient.PropertyChanged += VNClient_PropertyChanged;
MainWindow.ApplicationLoaded += MainWindow_ApplicationLoaded;
}
private void MainWindow_ApplicationLoaded(object? sender, EventArgs e)
{
foreach (PropertyInfo property in typeof(VNClient).GetProperties())
{
BindingAttribute attr;
if ((attr = (BindingAttribute)property.GetCustomAttribute(typeof(BindingAttribute), false)) != null)
{
staticReferenceBindings.Add(property.Name, null);
}
}
FindBindings(VNClient.MainWindowInstance);
}
private async void VNClient_PropertyChanged(object? sender, (string bindTag, DependencyProperty bindProperty, dynamic value) e)
{
foreach (KeyValuePair<string, List<FrameworkElement>> Binding in staticReferenceBindings)
{
if (Binding.Value == null) continue;
foreach (FrameworkElement element in Binding.Value)
{
DependencyProperty modifiedProperty = e.bindProperty;
//Property conversion for different elements... like background property => fill property
if (Binding is Shape && e.bindProperty == Control.BackgroundProperty) modifiedProperty = Shape.FillProperty;
else if (Binding is Window && e.bindProperty == TextBlock.TextProperty) modifiedProperty = Window.TitleProperty;
if (modifiedProperty != null) element.SetValue(modifiedProperty, e.value);
}
}
}
internal void FindBindings(DependencyObject parent)
{
int childCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childCount; i++)
{
DependencyObject dpObject = VisualTreeHelper.GetChild(parent, i);
FrameworkElement child = dpObject as FrameworkElement;
if (child != null)
{
string childTag = child.Tag?.ToString();
if (childTag != null && staticReferenceBindings.ContainsKey(childTag))
{
if (staticReferenceBindings[childTag] == null) staticReferenceBindings[childTag] = new List<FrameworkElement>();
staticReferenceBindings[childTag].Add(child);
}
}
FindBindings(dpObject);
}
}
}
Here is an example property:
internal static event EventHandler<(string bindTag, DependencyProperty bindProperty, dynamic value)> PropertyChanged;
private static string _gameName = "*Insert name here :)*";
[BindingAttribute(nameof(GameName))]
public static string GameName
{
get
{
return _gameName;
}
set
{
if (_gameName != value) _gameName = value;
OnPropertyChanged(nameof(GameName), TextBlock.TextProperty, value);
}
}
private static void OnPropertyChanged(string bindTag, DependencyProperty bindProperty, dynamic value) => PropertyChanged?.Invoke(Application.Current, (bindTag, bindProperty, value));
And here is that property bound to a TextBlock:
<TabItem Height="60" Width="250" BorderThickness="1" Background="Transparent" BorderBrush="Black" Foreground="White">
<TabItem.Header>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Image HorizontalAlignment="Left" Source="/Res/info_96px.png" Margin="0,0,180,0"/>
<TextBlock Text="About" HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center" TextTrimming="None" Foreground="#BFFFFFFF"/>
</Grid>
</TabItem.Header>
<StackPanel>
<Image Source="/Res/Logo.png" HorizontalAlignment="Center" VerticalAlignment="Top" Height="150" Width="150"/>
<TextBlock Text="Made with *Insert name here :)*" HorizontalAlignment="Center"/>
<WrapPanel HorizontalAlignment="Center">
<!-- EXAMPLE BINDING --><TextBlock Text="{x:Static local:VNClient.GameName}" Tag="GameName" Margin="0,30,5,0" HorizontalAlignment="Center"/>
<TextBlock Text="was made with *Insert name here :)* version:" Margin="0,30,0,0" HorizontalAlignment="Center"/>
<TextBlock Text="{x:Static local:VNClient.EngineVersion}" Tag="EngineVersion" Margin="5,30,0,0" HorizontalAlignment="Center"/>
</WrapPanel>
</StackPanel>
</TabItem>
(static binding is so i can see the binding in the VS editor)
Okay, so, everything works fine BUT when this XAML element is in a TabItem my recursive search can only find the
<TabItem.Header/>
content NOT the
<TabItem.Content/>
meaning the bindings won't update... which is kinda not good...
If anyone has any idea besides "Just use the normal bindings..." that would be amazing
Thanks in advance :)
(and sorry if this is hard to read i am dyslexic)
EDIT:
I got it working by explicitly specifying, if it's a TabItem start another recursive search through it's content before continuing with it's header (it's a bandaid solution for sure, but I have yet to find bugs or similar problems with other controls like tab items)
[search result before]
Not all bindings found
[search result after]
As far as I can tell, all bindings found
[Modified recursive method in BindingManager]
internal void FindBindings(DependencyObject parent)
{
int childCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childCount; i++)
{
DependencyObject dpObject = VisualTreeHelper.GetChild(parent, i);
FrameworkElement child = dpObject as FrameworkElement;
if (child != null)
{
string childTag = child.Tag?.ToString();
if (childTag != null && staticReferenceBindings.ContainsKey(childTag))
{
if (staticReferenceBindings[childTag] == null) staticReferenceBindings[childTag] = new List<FrameworkElement>();
staticReferenceBindings[childTag].Add(child);
}
}
//New condition here
if (child is TabItem && ((TabItem)child).Content != null)
{
DependencyObject tabContent = ((TabItem)child).Content as DependencyObject;
FindBindings(tabContent);
}
FindBindings(dpObject);
}
}
if anyone still as anything to add to this or a more universal solution pls don't hesitate to comment.
What I want
So I want to check 2 ObservableCollections if they equals each other.
If so, then return Nothing Changed (collection1 and collection2 are the same).
Otherwise return Something Changed.
The Problem
The problem now is, that both collection contains the same values even when I change items from collection 2.
I posted some Code and gif of the Debug result to show you what I get.
I dont understand, why both Collections are the same after clicking the Save Button.
Code
ViewModel
In my ViewModel I have:
1 ObservableCollection called RightsCollection.
This should contain the rights on my XAML which I can change via ToggleButton.
1 Employee class where a ObservableCollection<Groups> is located and inside of the Groups.Col there is a ObservableCollection<Rights> which contains the default group rights which was loaded from DataBase which cant be changed.
Note: My get set is always the same. They just have other names and DataTypes consider to its field datatype.
private Employee _singleEmployee = new Employee();
public Employee SingleEmployee
{
get => _singleEmployee;
set
{
if (_singleEmployee == value) return;
_singleEmployee = value;
OnPropertyChanged("SingleEmployee");
}
}
private ObservableCollection<Groups> _groupsCollection = new ObservableCollection<Groups>();
// public get set GroupsCollection (same like first).
private ObservableCollection<Rights> _rightsCollection = new ObservableCollection<Rights>();
// public get set RightsCollection (same like first).
Employee Class
public class Employee : INotifyPropertyChanged
{
private int _employeeId;
private string _firstName;
private Groups _group = new Group();
// public get set EmployeeId (Same like first).
// public get set Group (same like first).
}
Rights Class
private int _rightId;
private string _rightName;
private bool _hasRight;
// Again get set is same
Groups Class
private int _groupId;
private string _groupName;
private ObservableCollection<Rights> _rights;
// Again, same Get/Set like always
XAML
In my XAML I have:
a ComboBox. ComboBox.ItemsSource bind to GroupsCollection. ComboBox.SelectedValue bind to SingleEmployee.Group.
So while changing the ComboBox, the Group of the Single Employee will be set.
This ComboBox also got an SelectionChanged Event where I set the RightsCollection equal to SingleEmployee.Group.Rights. So that both contains the same items/values now.
It also contains an ItemsControl where I can set the rights myself (and where the rights will be loaded when ComboBox.SelectionChanged (which works).
<ComboBox x:Name="GroupComboBox" ItemsSource="{Binding GroupsCollection}" SelectedValue="{Binding SingleEmployee.Group}" DisplayMemberPath="GroupName" SelectionChanged="GroupComboBox_SelectionChanged">
ItemsControl
<ItemsControl ItemsSource="{Binding RightsCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<DockPanel>
<ToggleButton DockPanel.Dock="Right" Margin="10" IsChecked="{Binding HasRight}"/>
<TextBlock FontSize="15" FontWeight="Bold" Text="{Binding RightName}" DockPanel.Dock="Left" Margin="10" />
</DockPanel>
<TextBlock Text="{Binding RightsDesc}" Margin="30 0 0 10" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
SelectionChanged Event in Code-Behind
Debug.WriteLine("############ SelectionChanged Event ############");
Debug.WriteLine("# Before Change ##");
Debug.WriteLine($"SingleEmployee.Group.Rights.Count: {_viewModel.SingleEmployee.Group.Rights.Count} | RightsCollection.Count: {_viewModel.RightsCollection.Count}");
for (int i = 0; i < _viewModel.SingleEmployee.Group.Rights.Count; i++)
{
Debug.WriteLine($"Name: {_viewModel.SingleEmployee.Group.Rights[i].RightName}, HasRight: {_viewModel.SingleEmployee.Group.Rights[i].HasRight} || Name: {_viewModel.RightsCollection[i].RightName}, HasRight: {_viewModel.RightsCollection[i].HasRight}");
}
_viewModel.RightsCollection = _viewModel.SingleEmployee.Group.Rights;
Debug.WriteLine("# After Change #");
Debug.WriteLine($"SingleEmployee.Group.Rights.Count: {_viewModel.SingleEmployee.Group.Rights.Count} | RightsCollection.Count: {_viewModel.RightsCollection.Count}");
for (int i = 0; i < _viewModel.SingleEmployee.Group.Rights.Count; i++)
{
Debug.WriteLine$"Name: {_viewModel.SingleEmployee.Group.Rights[i].RightName}, HasRight: {_viewModel.SingleEmployee.Group.Rights[i].HasRight} || Name: {_viewModel.RightsCollection[i].RightName}, HasRight: {_viewModel.RightsCollection[i].HasRight}");
}
Debug.WriteLine("########## SelectionChanged Event END ##########");
Debug.WriteLine("################################################");
Set ViewModel in Code-Behind
private readonly EmployeeViewModel _viewModel;
// constructor...
{
_viewModel = (EmployeeViewModel) DataContext;
}
Save Button Command Method
Debug.WriteLine("############## After Button Click ##############");
for (int i = 0; i < RightsCollection.Count; i++)
{
Debug.WriteLine($"Name: {SingleEmployee.Group.Rights[i].RightName}, HasRight: {SingleEmployee.Group.Rights[i].HasRight} || Name: {RightsCollection[i].RightName}, HasRight: {RightsCollection[i].HasRight}");
}
Debug.WriteLine("################################################");
bool equal = RightsCollection.Count == SingleEmployee.Group.Rights.Count && RightsCollection.All(x => SSingleEmployee.Group.Rights.Contains(x));
Debug.WriteLine(equal ? "Nothing Changed" : "Something changed");
What I tried
SelectionChanged Event
// No Success
var collection = new ObservableCollection<Rights>(_viewModel.SingleEmployee.Group.Rights);
_viewModel.RightsCollection = collection;
.
// No Success
foreach(var item in _viewModel.SingleEmployee.Group.Rights)
_viewModel.RightsCollection.Add(item);
Result of the Debugging
SelectionChangedResult
|
SelectionChangedResult
After Button Click
After Button Click
_viewModel.RightsCollection = _viewModel.SingleEmployee.Group.Rights;
the left collection has the same reference of the right collection.
So, if you change one collection it will reflect in the other collection.
ObservableCollection<Whatever> _viewModel.RightsCollection = new ObservableCollection<Whatever>();
foreach(var item in _viewModel.SingleEmployee.Group.Rights)
_viewModel.RightsCollection.Add(item);
I fixed this problem in my SelectionChanged Event deleting this line:
_viewModel.RightsCollection = _viewModel.SingleEmployee.Group.Rights;
and replaced it with this:
for (int i = 0; i < _viewModel.SingleEmployee.Group.Rights.Count; i++)
{
if (_viewModel.SingleEmployee.Group.Rights[i].HasRight != _viewModel.RightsCollection[i].HasRight)
{
_viewModel.RightsCollection[i].HasRight = _viewModel.SingleEmployee.Group.Rights[i].HasRight;
}
}
Because both collections are nearly the same, they will always have the same amount of items so I can use a for-loop.
If a value is not the same, then the value will change.
This way I don't create a reflection (I guess) so it's working.
The only thing now is, that
bool equal = RightsCollection.Count == SingleEmployee.Group.Rights.Count && RightsCollection.All(x => SingleEmployee.Group.Rights.Contains(x));
isn't working but I will use a for-loop here too which checks if the items contains the same value, if not then "Something changed".
I have a row in a grid with 5 textboxes, 2 of which are enabled by checkboxes. I am trying to dynamically add additional rows to the grid when a button is clicked. The eventhandler I added will only enable the textbox in the first row, but not in the current row (2nd). There is another eventhandler which handles the box in the first row, this is a new one. (BTW I only have part of the second row coded). Not sure if I should try making a template for the checkbox, and then use binding to the textbox? And if so, the instructions I've read on connecting the binding are vague and confusing. Or can I do the binding directly? Or ?
public partial class Window2 : Window
{
int currentColumn = 0;
int currentRow = 1;
int timesCalled = 1;
public Window2()
{
InitializeComponent();
}
private void AddLevelButton_Click(object sender, RoutedEventArgs e)
{
string level = this.Level.Content.ToString(); //label for the row
string[] splitLevel = level.Split(' ');
int levelNum = int.Parse(splitLevel[1]);
levelNum = timesCalled + 1;
int nextRow = currentRow + 1;
int nextColumn = currentColumn + 1;
Label levelLabel = new Label();
levelLabel.Content = "Level " + levelNum.ToString();
Grid.SetRow(levelLabel, nextRow);
Grid.SetColumn(levelLabel, currentColumn);
FlowGrid.Children.Add(levelLabel);
currentColumn++;
CheckBox antesBox = new CheckBox(); //the checkbox to enable the
antesBox.Name = "AntesBox"; //textbox which follows
antesBox.VerticalAlignment = VerticalAlignment.Bottom;
antesBox.HorizontalAlignment = HorizontalAlignment.Right;
antesBox.FontSize = 16;
antesBox.Width = 20;
antesBox.Height = 20;
antesBox.Checked += AntesBox_Checked1; //eventhandler
Grid.SetRow(antesBox, nextRow);
Grid.SetColumn(antesBox, currentColumn);
FlowGrid.Children.Add(antesBox);
nextColumn = ++currentColumn;
TextBox enterAntes = new TextBox(); //the textbox to be enabled
enterAntes.Name = "EnterAntes";
enterAntes.Margin = new Thickness(5, 0, 5, 0);
enterAntes.FontSize = 16;
enterAntes.FontFamily = new FontFamily("Verdana");
enterAntes.IsEnabled = false;
enterAntes.KeyDown += EnterAntes_KeyDown1; //tested; this works
Grid.SetRow(EnterAntes, nextRow);
Grid.SetColumn(EnterAntes, nextColumn);
FlowGrid.Children.Add(EnterAntes);
nextColumn = ++currentColumn;
}
private void enterAntes_KeyDown1(object sender, KeyEventArgs e)
{
int key = (int)e.Key;
e.Handled = !(key >= 34 && key <= 43 ||
key >= 74 && key <= 83 || key == 2);
}
private void AntesBox_Checked1(object sender, RoutedEventArgs e)
{
EnterAntes.IsEnabled = true;
}
You need to add following codes to enable text boxes.
Following is the xaml view of the datagrid.
<DataGrid x:Name="gvTest" AutoGenerateColumns="False" ItemsSource="{Binding}" HorizontalAlignment="Left" Margin="86,204,0,0" VerticalAlignment="Top" Height="132" Width="436">
<DataGrid.Columns>
<DataGridTemplateColumn Header="TextBox 01">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txt01" Width="50" Text="{Binding TxtBox01}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 02">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox02" Width="50" Text="{Binding TxtBox02}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 03">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox03" Width="50" Text="{Binding TxtBox03}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 04">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox04" Width="50" IsEnabled="False" Text="{Binding TxtBox04}" Loaded="txtbox04_Loaded"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 05">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox05" Text="{Binding TxtBox05}" Loaded="txtbox05_Loaded"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Enable" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="chk01" Checked="chk01_Checked" IsChecked="{Binding IsActive}"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
add the following codes to declare instance of required textboxes and declare observable collection.
TextBox txt04;
TextBox txt05;
ObservableCollection<TestItem> TestItemList = new ObservableCollection<TestItem>();
add the following codes to the loaded event of the required textboxes.
private void txtbox04_Loaded(object sender, RoutedEventArgs e)
{
txt04 = (sender as TextBox);
//txt04.IsEnabled = false;
}
private void txtbox05_Loaded(object sender, RoutedEventArgs e)
{
txt05 = (sender as TextBox);
}
Now, create a model class with following code segment in order to bind values to the datagrid.
public class TestItem
{
public string TxtBox01 { get; set; }
public string TxtBox02 { get; set; }
public string TxtBox03 { get; set; }
public string TxtBox04 { get; set; }
public string TxtBox05 { get; set; }
public bool IsActive { get; set; }
public TestItem()
{
IsActive = false;
}
}
I have used a button to add new rows to the datagrid. add the following codes to the button click to add rows.
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
TestItemList.Add(new TestItem());
gvTest.ItemsSource = TestItemList;
}
Finally, add the following codes to the checkbox checked event
CheckBox c = (sender as CheckBox);
if (c.IsChecked==true)
{
txt04.IsEnabled = true;
txt05.IsEnabled = true;
}
Hope this helps you to fulfill your requirement.
At the risk of perpetuating the wrong approach, it seems to me that the most direct way to address your specific need here is to fix your event handler so that it is always specific to the text box that corresponds to the checkbox in question. This is most easily done by moving the event handler subscription to below the declaration of the local variable enterAntes, and then use that variable in the event handler (i.e. so that it's capture by the anonymous method used as the event handler). For example:
TextBox enterAntes = new TextBox(); //the textbox to be enabled
antesBox.Checked += (sender, e) => enterAntes.IsEnabled = true;
Now, that said, I whole-heartedly agree with commenter Mark Feldman who suggests that the code you've written is not the right way to accomplish your goal in WPF.
I'm not sure I agree with the characterization "harder". That's such a loaded and subjective term, depending in no small part in what you find easy or hard. Being new to WPF, you almost certainly find the concepts of data binding and declarative XAML-based coding "hard", and direct, procedural code such as in your example "easy" (or at least "easier" :) ).
But he's absolutely right that in the long run, you will be better served by doing things "the WPF way". You may or may not wind up with much less code, but the WPF API is designed to be leveraged as much as possible from the XAML, and use code-behind minimally (and certainly not for the purpose to build the UI).
So what's all that mean for your code? Well, I ramble and it would be beyond the scope of a good, concise Stack Overflow answer for me to try to rewrite your entire code from scratch to suit the WPF paradigm. But I will offer some suggestions as to how I'd handle this.
First, forget the UI objects themselves for a moment. Write classes that describe the key characteristics of the UI as you want it to be, without being the UI itself. In this example, this could mean that you should have a list of rows. There should also be a class that defines what a single row looks like, e.g. with a bool property (to reflect the checkbox state) and a string property (to reflect the text box value). This is your "model"; i.e. each class is an individual model class, and at the same time you could consider the entire collection of classes as the model for your UI.
Now, go back to your UI and define it in XAML, not in code. There are several different ways to represent a list in the UI. Classes like ListBox, ListView, DataGrid, or even ItemsControl (the base class for many of the list-oriented controls). Bind the source of your list control to the model list you created in the previous step.
Define a DataTemplate (again, in XAML) for the type of class that is contained in the list. This will declare the UI for a single row in your list. Your template might look something like this:
<!-- Make sure you defined the "local" XML namespace for your project using the
xmlns declaration -->
<DataTemplate DataType="{x:Type local:MyRowModel}">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Text}" IsEnabled={Binding IsEnabled}"/>
<Checkbox Checked="{Binding IsEnabled}"/>
</StackPanel>
</DataTemplate>
All of the XAML inside the DataTemplate element tells WPF what you want a single row to look like, within the control that is presenting your row model. That control will set the DataContext for the list item defined by the template, such that the {Binding...} declarations can reference your row model's properties directly by name.
That row model in turn might look something like this:
class MyRowModel : INotifyPropertyChanged
{
private string _text;
private bool _isEnabled;
public string Text
{
get { return _text; }
set
{
if (_text != value)
{
_text = value;
OnPropertyChanged();
}
}
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (_isEnabled != value)
{
_isEnabled = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
When your button to add a new item is clicked, don't mess with the UI directly. Instead, add a new element to your list of rows. Let WPF do the work of updating the UI to match.
NOTES:
The above uses StackPanel for the data template for convenience. If you want things lined up in columns, you'll probably want to use a Grid and declare its columns using SharedSizeGroup.
Or better yet, maybe you can use DataGrid which, assuming its default presentation of the values is acceptable to you, offers simple and automatic handling of exactly this type of layout.
The above is not meant to be anything close to a complete explanation of how to use data templating. It's just supposed to get you pointed in the right direction. Templating is one of WPF's more powerful features, but with that it also has the potential to be fairly complex.
For all of this to work, your types need to provide notification when they change. In the case of the row model, you can see it implements INotifyPropertyChanged. There is also an interface INotifyCollectionChanged; usually you don't have to implement this yourself, as WPF has the type ObservableCollection<T> which you can use just like List<T>, to store lists of data but with a way for notifications of changes to be reported to WPF.
I know this is a lot to take it all at once. Unfortunately, it's not feasible to try to explain all in a single answer all the things you need to learn to get this right. Frankly, even the above is pushing the limits as to what's within the scope of a Stack Overflow answer. But I hope that I've hit just the right highlights to get you looking at the right parts of the WPF documentation, and to understand the underlying philosophy of the WPF API.
I'm creating a Polyline in a WPF canvas that is suppose to update it's position, when hitting on a button, based on calculations that happends in my model. I'm using a MVVM pattern.
My XAML code:
<Grid>
<StackPanel>
<Button Margin="10" Command="{Binding Path=RunAnalysisCmd}">Rune analysis!</Button>
<Canvas>
<Polyline Points="{Binding ModelPathPoints, UpdateSourceTrigger=PropertyChanged}" Stroke="Blue" StrokeThickness="2"/>
</Canvas>
</StackPanel>
</Grid>
In my ViewModel I have a PointCollection property where the path points are stored.
private PointCollection _modelPathPoints = new PointCollection();
public PointCollection ModelPathPoints
{
get { return _modelPathPoints; }
set
{
_modelPathPoints = value;
NotifyPropertyChanged("ModelPathPoints");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
The method RunAnalysis works fin, and I have tested it using Consol output. My problems are that the canvas is not changing when the points in the PointCollection are changing.
public void RunAnalysis()
{
double angle = 0;
for (int i = 0; i < 1000; i=i+10)
{
Model.TranslateModelEdgeNodes(i, i);
Model.RotateModelEdgeNodes(angle);
angle = angle + 0.1;
AddModelPointsToPointCollection();
System.Threading.Thread.Sleep(500);
}
}
public void AddModelPointsToPointCollection()
{
//ModelPathPoints.Clear();
PointCollection modelPathPoints = new PointCollection();
for (int i = 0; i < Model.ModelEdgeNodes.Count(); i++)
{
modelPathPoints.Add(new Point( XXX, XXX )) // Not important what XXX, XXX is
}
modelPathPoints.Add(new Point(XXX, XXX )); // Not important what XXX, XXX is
ModelPathPoints = modelPathPoints;
}
Does anyone see the problem??
To avoid wondering whether Thread.Sleep is interfering with the UI doing it's thing, why don't you use a click-event, that will make it do one iteration that should be visibly apparent?
Within that property-setting, you could then verify that it is being assigned with each click, verify the values of the points being assigned, and that the PropertyChanged event is happening.
Oh - and I assume your view-model implements INotifyPropertyChanged, and that you're assigning an actual value to your PropertyChanged event somewhere that you're not showing in your code above?
Let me know if that helps - if not, I'll create a project and check this for you. Best.
How can I access a Canvas control, stored in DataTemplate of GridView items from my C# code?
<DataTemplate x:Key="250x250ItemTemplate">
<Grid HorizontalAlignment="Left" Width="250" Height="250">
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Canvas x:Name="Canv"/> <------ I WANT ACCESS THIS CANVAS FROM C# CODE
</Border>
</Grid>
</DataTemplate>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<GridView x:Name="GridViewData" ItemTemplate="{StaticResource 250x250ItemTemplate}"/>
</Grid>
I'm filling GridViewData items from C# code, setting GridViewData.ItemsSource with data from remotely loaded XML.
Then I need to modify Canvas (by adding children to it) of each element separately.
But I don't understand how can I do that.
Can anyone help me with it?
Thank you in advance!
Everyone who interested in answering this question!
I've found a solution here: http://www.wiredprairie.us/blog/index.php/archives/1730
It's horrible that I don't understand why we need to do so much magic here, but it works.
namespace Extension
{
public static class FrameworkElementExtensions
{
public static FrameworkElement FindDescendantByName(this FrameworkElement element, string name)
{
if (element == null || string.IsNullOrWhiteSpace(name))
{
return null;
}
if (name.Equals(element.Name, StringComparison.OrdinalIgnoreCase))
{
return element;
}
var childCount = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < childCount; i++)
{
var result = (VisualTreeHelper.GetChild(element, i) as FrameworkElement).FindDescendantByName(name);
if (result != null)
{
return result;
}
}
return null;
}
}
}
and
for (int i = 0; i<GridViewTickers.Items.Count; i++)
{
var element = GridViewTickers.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
if (element != null)
{
var tb = element.FindDescendantByName("Canv") as Canvas;
if (tb != null)
{
TextBlock tb1 = new TextBlock();
tb1.Text = "hello";
tb.Children.Add(tb1);
}
}
}
If anyone can explain for me what we're doing in this bunch of code - please do this, 'cause my brain is exploding right now :)
Thank you all!