Listbox inside pivot - c#

I've got this code. I need to have access to the ScheduleList from my c# code. But it's inaccessible. I can get access to SchedulePivot only.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="0,0,0,50">
<Pivot x:Name="SchedulePivot" Margin="10,10,10,0" Title="Pivot" VerticalAlignment="Top">
<Pivot.ItemTemplate>
<DataTemplate>
<ListBox x:Name="ScheduleList" Margin="0,0,0,17" Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="52" Width="auto">
Searching on StackOverflow I have found this code:
private DependencyObject FindChildControl<T>(DependencyObject control, string ctrlName)
{
int childNumber = VisualTreeHelper.GetChildrenCount(control);
for (int i = 0; i < childNumber; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(control, i);
FrameworkElement fe = child as FrameworkElement;
// Not a framework element or is null
if (fe == null) return null;
if (child is T && fe.Name == ctrlName)
{
// Found the control so return
return child;
}
else
{
// Not found it - search children
DependencyObject nextLevel = FindChildControl<T>(child, ctrlName);
if (nextLevel != null)
return nextLevel;
}
}
return null;
}
I use this line to get the child:
ListBox listCont = FindChildControl<ListBox>(this, "ScheduleList") as ListBox;
Also I tried doing like this:
ListBox listCont = FindChildControl<ListBox>(SchedulePivot, "ScheduleList") as ListBox;
than I do this:
listCont.Items.Add(items);
And get the exeption as listCont=null. What's wrong I'm doing?

I have tested your code, both of the following code work well in my side and I can get the correct result:
ListBox listCont = FindChildControl<ListBox>(this, "ScheduleList") as ListBox;
ListBox listCont = FindChildControl<ListBox>(SchedulePivot, "ScheduleList") as ListBox;
If we want to access the control by using the VisualTreeHelper, we should make sure that we have not called the above code inside the constructor of the MainPage, or we will get the null result as below. Because the control does not been initialized completely:
In order to get the correct result, we need to call the above code inside the MainPage.Loaded event or Button click event to make sure that control has been initialized completely, after that it should work fine.
The following is my sample, please try to refer to:
In the MainPage.xaml:
<Pivot x:Name="SchedulePivot" ItemsSource="{Binding PivotTestlist}" Margin="10,10,10,0" Title="Pivot" VerticalAlignment="Top">
<Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding header}"></TextBlock>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<ListBox x:Name="ScheduleList" Margin="0,0,0,17" Width="Auto" ItemsSource="{Binding ListBoxTestlist}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="52" Width="auto">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding id}"></TextBlock>
<TextBlock Text="{Binding name}"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</Pivot.ItemTemplate>
</Pivot>
<Button Click="Button_Click" Content="Button"></Button>
In the MainPage.xaml.cs:
public class ListBoxTest
{
public string name { get; set; }
public string id { get; set; }
}
public class PivotTest
{
public List<ListBoxTest> ListBoxTestlist { get; set; }
public string header { get; set; }
}
public sealed partial class MainPage : Page
{
public List<PivotTest> PivotTestlist { get; set; }
public MainPage()
{
this.InitializeComponent();
PivotTestlist = new List<PivotTest>();
PivotTest PivotTest1 = new PivotTest();
PivotTest1.ListBoxTestlist = new List<ListBoxTest>();
PivotTest1.ListBoxTestlist.Add(new ListBoxTest() { name = "name1", id = "id1" });
PivotTest1.ListBoxTestlist.Add(new ListBoxTest() { name = "name2", id = "id2" });
PivotTest1.header = "header1";
PivotTestlist.Add(PivotTest1);
PivotTest PivotTest2 = new PivotTest();
PivotTest2.ListBoxTestlist = new List<ListBoxTest>();
PivotTest2.ListBoxTestlist.Add(new ListBoxTest() { name = "name11", id = "id11" });
PivotTest2.ListBoxTestlist.Add(new ListBoxTest() { name = "name22", id = "id22" });
PivotTest2.header = "header2";
PivotTestlist.Add(PivotTest2);
this.DataContext = this;
}
private DependencyObject FindChildControl<T>(DependencyObject control, string ctrlName)
{
int childNumber = VisualTreeHelper.GetChildrenCount(control);
for (int i = 0; i < childNumber; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(control, i);
FrameworkElement fe = child as FrameworkElement;
// Not a framework element or is null
if (fe == null) return null;
if (child is T && fe.Name == ctrlName)
{
// Found the control so return
return child;
}
else
{
// Not found it - search children
DependencyObject nextLevel = FindChildControl<T>(child, ctrlName);
if (nextLevel != null)
return nextLevel;
}
}
return null;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ListBox listCont = FindChildControl<ListBox>(SchedulePivot, "ScheduleList") as ListBox;
int count = listCont.Items.Count;
}
}
The result:

Declare the Pivot in a storyboard and use the x:Key instead of x:Name.
e.g.
<StoryBoard>
<Pivot x:key="nameIt"/>
</StoryBoard>
private void AccesPivot ()
{ //now you can acces your pivot
}

Related

FindVisualChild - Get properties of named UI Elements in ItemsControl

I generate UI elements at runtime by using a ItemsControl. The UI generates successfully but if I am unable to get any properties of the generated UI items, such as "Content" for a label, or SelectedItem for a ComboBox. I tried to get these properties by using this tutorial , and these answers but I always get a NullReferenceException.
The ItemsControl in XAML looks like this:
<ItemsControl Name="ListOfVideos">
<ItemsControl.Background>
<SolidColorBrush Color="Black" Opacity="0"/>
</ItemsControl.Background>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180"/>
<ColumnDefinition Width="400"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Image HorizontalAlignment="Left" Height="100" Width="175" x:Name="VideoThumbnailImage" Stretch="Fill" Source="{Binding VideoThumbnailURL}" Grid.Column="0"></Image>
<Label x:Name="VideoTitleLabel" Content="{Binding VideoTitleText}" Foreground="White" Grid.Column="1" VerticalAlignment="Top" FontSize="16" FontWeight="Bold"></Label>
<Label x:Name="VideoFileSizeLabel" Content="{Binding VideoTotalSizeText}" Foreground="White" FontSize="14" Grid.Column="1" Margin="0,0,0,35" VerticalAlignment="Bottom"></Label>
<Label x:Name="VideoProgressLabel" Content="{Binding VideoStatusText}" Foreground="White" FontSize="14" Grid.Column="1" VerticalAlignment="Bottom"></Label>
<ComboBox x:Name="VideoComboBox" SelectionChanged="VideoComboBox_SelectionChanged" Grid.Column="2" Width="147.731" Height="20" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,50" ItemsSource="{Binding VideoQualitiesList}"></ComboBox>
<Label Content="Video Quality" Foreground="White" FontSize="14" VerticalAlignment="Top" Grid.Column="2" HorizontalAlignment="Center"></Label>
<Label Content="Audio Quality" Foreground="White" FontSize="14" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,27" Grid.Column="2"></Label>
<Slider x:Name="VideoAudioSlider" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Bottom" Width="147.731" Maximum="{Binding AudioCount}"></Slider>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is how I generate the UI elements
public class VideoMetadataDisplay
{
public string VideoTitleText { get; set; }
public int AudioCount { get; set; }
public string VideoThumbnailURL { get; set; }
public string VideoStatusText { get; set; }
public string VideoTotalSizeText { get; set; }
public List<string> VideoQualitiesList { get; set; }
}
public partial class PlaylistPage : Page
{
private void GetPlaylistMetadata()
{
List<VideoMetadataDisplay> newList = new List<VideoMetadataDisplay>();
//populate the list
ListOfVideos.ItemsSource = newList;
}
}
And this is how I'm trying to get the properties of the UI elements
public class Utils
{
public childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
{
return (childItem)child;
}
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
}
private void VideoComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
UIElement CurrentItem = (UIElement)ListOfVideos.ItemContainerGenerator.ContainerFromItem(ListOfVideos.Items.CurrentItem);
Utils utils = new Utils();
ContentPresenter CurrentContentPresenter = utils.FindVisualChild<ContentPresenter>(CurrentItem);
DataTemplate CurrentDataTemplate = CurrentContentPresenter.ContentTemplate;
Label VideoTitle = (Label)CurrentDataTemplate.FindName("VideoTitleLabel", CurrentContentPresenter);
string VideoTitleText = VideoTitle.Content.ToString();
MessageBox.Show(VideoTitleText);
}
Every time I try to run this, FindVisualChild always returns one of the labels (VideoTitleLabel) instead of returning the ContentPresenter for the currently active item. CurrentDataTemplate is then null and I am unable to get any of the UI elements from it.
It is impossible that FindVisualChild<ContentPresenter> returns a Label instance. FindVisualChild casts the result to ContentPresenter. Since Label is not a ContentPresenter, this would throw an InvalidCastException. But before this, child is childItem would return false in case child is of type Label and the generic parameter type childItem is of type ContentPresenter and therefore a potential null is returned.
Short Version
Accessing the the DataTemplate or looking up controls just to get their bound data is always too complicated. It's always easier to access the data source directly.
ItemsControl.SelectedItem will return the data model for the selected item. You are usually not interested in the containers.
private void VideoComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = sender as ListView;
var item = listView.SelectedItem as VideoMetadataDisplay;
MessageBox.Show(item.VideoTitleText);
}
Your Version (FindVisualChild improved)
FindVisualChild is weakly implemented. It will fail and throw an exception if traversal encounters a child node without children i.e. the parameter obj is null. You have to check the parameter obj for null before invoking VisualTreeHelper.GetChildrenCount(obj), to avoid a null reference.
Also you don't need to search the element by accessing the template. You can look it up directly in the visual tree.
I have modified your FindVisualChild method to search elements by name. I also have turned it into an extension method for convenience:
Extension Method
public static class Utils
{
public static bool TryFindVisualChildByName<TChild>(
this DependencyObject parent,
string childElementName,
out TChild childElement,
bool isCaseSensitive = false)
where TChild : FrameworkElement
{
childElement = null;
// Popup.Child content is not part of the visual tree.
// To prevent traversal from breaking when parent is a Popup,
// we need to explicitly extract the content.
if (parent is Popup popup)
{
parent = popup.Child;
}
if (parent == null)
{
return false;
}
var stringComparison = isCaseSensitive
? StringComparison.Ordinal
: StringComparison.OrdinalIgnoreCase;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (child is TChild resultElement
&& resultElement.Name.Equals(childElementName, stringComparison))
{
childElement = resultElement;
return true;
}
if (child.TryFindVisualChildByName(childElementName, out childElement))
{
return true;
}
}
return false;
}
}
Example
private void VideoComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = sender as ListView;
object item = listView.SelectedItem;
var itemContainer = listView.ItemContainerGenerator.ContainerFromItem(item) as ListViewItem;
if (itemContainer.TryFindVisualChildByName("VideoTitleLabel", out Label label))
{
var videoTitleText = label.Content as string;
MessageBox.Show(videoTitleText);
}
}

How to modify binding-path at WPF application runtime

I want to change the binding path SomeProperty to another at runtime. And the PropertyName can be any string ,so I can't set it before it running. how to do this?
I tried "FindName" method to find tb1 but it's not work.
Partical code :
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="tb1" Text="{Binding Path=SomeProperty}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Maybe you're simply looking for "DisplayMemberPath"?
<ListBox ItemsSource="{Binding Items}" DisplayMemberPath="SomeProperty" />
You cannot mix it with DataTemplates but in your example you didn't really need it.
You can create a normal binding for this property:
<StackPanel>
<ListBox ItemsSource="{Binding Items}"
DisplayMemberPath="{Binding ElementName=DisplayPathText, Path=Text}" />
<TextBox Name="DisplayPathText" Text="SomeProperty" />
</StackPanel>
Due to the lack of context, let's try to establish one.
Assuming your ListBox is binding to a Person list with FirstName and LastName property, initial binding would be FirstName and you want to change it to LastName during runtime when clicking on a button.
This is how you can achieve it.
View
<ListBox Name="LstBx" ItemsSource="{Binding PersonList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="tb1" Text="{Binding Path=FirstName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Click="Button_Click" Width="100" Height="20" Content="Change Binding"/>
CodeBehind
public List<Person> PersonList { get; set; } = new List<Person>
{
new Person { FirstName = "ABC", LastName = "123" },
new Person { FirstName = "DEF", LastName = "456" },
new Person { FirstName = "GHI", LastName = "789" }
};
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
foreach (var person in PersonList)
{
var listBoxItem = LstBx.ItemContainerGenerator.ContainerFromItem(person);
var contentPresenter = FindVisualChild<ContentPresenter>(listBoxItem);
var target = contentPresenter.ContentTemplate.FindName("tb1", contentPresenter) as TextBox;
if (target != null)
{
var binding = new Binding
{
// Remember each ListBoxItem's DataContext is the individual item
// in the list, not the list itself.
Source = person,
Path = new PropertyPath(nameof(Person.LastName)),
// Depends on what you need.
//Mode = BindingMode.TwoWay,
//UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
BindingOperations.SetBinding(target, TextBox.TextProperty, binding);
}
}
}
// Available from MSDN
private Child FindVisualChild<Child>(DependencyObject obj) where Child : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is Child)
{
return (Child)child;
}
else
{
var childOfChild = FindVisualChild<Child>(child);
if (childOfChild != null) { return childOfChild; }
}
}
return null;
}

Changing DataContext doesn't update list fields

I created a new TextBlock class which has ItemsSource property and translates that ItemsSource into "Run" object:
public class MultiTypeDynamicTextBlock : TextBlock
{
public interface ISection
{
Inline GetDisplayElement();
}
public class TextOption : ISection
{
private Run mText;
public TextOption(string aText)
{
mText = new Run();
mText.Text = aText.Replace("\\n", "\n");
}
public Inline GetDisplayElement()
{
return mText;
}
}
public class LineBreakOption : ISection
{
public Inline GetDisplayElement()
{
return new LineBreak();
}
public ISection Clone()
{
return new LineBreakOption();
}
}
public class ImageOption : ISection
{
private InlineUIContainer mContainer;
public ImageOption(string aDisplay)
{
Image lImage;
lImage = new Image();
lImage.Source = new BitmapImage(new Uri(Environment.CurrentDirectory + aDisplay));
lImage.Height = 15;
lImage.Width = 15;
mContainer = new InlineUIContainer(lImage);
}
public Inline GetDisplayElement()
{
return mContainer;
}
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(ObservableCollection<ISection>), typeof(MultiTypeDynamicTextBlock),
new UIPropertyMetadata(new ObservableCollection<ISection>(),
new PropertyChangedCallback(SetContent)));
public ObservableCollection<ISection> ItemsSource
{
get
{
return GetValue(ItemsSourceProperty) as ObservableCollection<ISection>;
}
set
{
if (ItemsSource != null)
ItemsSource.CollectionChanged -= CollectionChanged;
SetValue(ItemsSourceProperty, value);
SetContent();
ItemsSource.CollectionChanged += CollectionChanged;
}
}
private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
SetContent();
}
private static void SetContent(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DependencyObject lParent = d;
MultiTypeDynamicTextBlock lPanel = lParent as MultiTypeDynamicTextBlock;
if (lPanel != null)
{
lPanel.ItemsSource = e.NewValue as ObservableCollection<ISection>;
}
}
private void SetContent()
{
if (ItemsSource != null)
{
Inlines.Clear();
foreach (ISection lCurr in ItemsSource)
{
Inlines.Add(lCurr.GetDisplayElement());
}
}
}
If I Bind the ItemsSource directly to the DataContext, it works.
But if I bind it to an object that changes at runtime (such as SelectedItem on a ListBox) it doesn't update the text when a new item is selected.
<StackPanel>
<ListBox x:Name="TheList" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding ElementName=TheList, Path=SelectedItem}">
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock ItemsSource="{Binding Items}"/>
</StackPanel>
</StackPanel>
Any reason why?
In your example, does the SelectedItem has two properties Title and Items? Or is Items a property in your viewmodel? If the answer is the latter, than you can find a solution below.
I don't entirely understand what you mean, but I'll give it a try.
If you mean that the ItemsSource on your custom control isn't set, than you have to point XAML into the right direction.
Below you can find a solution, if this is what you want to achieve.
What I did is pointing the compiler to the right source with this line of code:
ItemsSource="{Binding DataContext.Items, RelativeSource={RelativeSource AncestorType=Window}}"
Here you say that the compiler can find the Binding property in the DataContext of the Window (or any control where you can find the property).
<StackPanel>
<ListBox x:Name="TheList" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding ElementName=TheList, Path=SelectedItem}">
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock ItemsSource="{Binding DataContext.Items, RelativeSource={RelativeSource AncestorType=Window}}"/>
</StackPanel>
</StackPanel>
Hopefully this helped.
EDIT
The title property will changes when I select another one from the ListBox.
If Items is set to a new ObservableCollection, do you call the OnPropertyChanged event for Items when the SelectedItem changes?
OnPropertyChanged("Items");
Thank you for your help.
I managed to fix this by updating the MultiTypeDynamicTextBlock as follows:
public class MultiTypeDynamicTextBlock : TextBlock
{
public interface ISection
{
Inline GetDisplayElement();
ISection Clone();
}
public class TextOption : ISection
{
private Run mText;
public TextOption(string aText)
{
mText = new Run();
mText.Text = aText.Replace("\\n", "\n");
}
public Inline GetDisplayElement()
{
return mText;
}
public ISection Clone()
{
return new TextOption(mText.Text);
}
}
public class LineBreakOption : ISection
{
public Inline GetDisplayElement()
{
return new LineBreak();
}
public ISection Clone()
{
return new LineBreakOption();
}
}
public class SectionList
{
private ObservableCollection<ISection> mList;
public Action CollectionChanged;
public ObservableCollection<ISection> Items
{
get
{
ObservableCollection<ISection> lRet = new ObservableCollection<ISection>();
foreach (ISection lCurr in mList)
{
lRet.Add(lCurr.Clone());
}
return lRet;
}
}
public int Count { get { return mList.Count; } }
public SectionList()
{
mList = new ObservableCollection<ISection>();
}
public void Add(ISection aValue)
{
mList.Add(aValue);
}
public SectionList Clone()
{
SectionList lRet = new SectionList();
lRet.mList = Items;
return lRet;
}
}
public MultiTypeDynamicTextBlock()
{
}
public static readonly DependencyProperty ItemsCollectionProperty =
DependencyProperty.Register("ItemsCollection", typeof(SectionList), typeof(MultiTypeDynamicTextBlock),
new UIPropertyMetadata((PropertyChangedCallback)((sender, args) =>
{
MultiTypeDynamicTextBlock textBlock = sender as MultiTypeDynamicTextBlock;
SectionList inlines = args.NewValue as SectionList;
if (textBlock != null)
{
if ((inlines != null) && (inlines.Count > 0))
{
textBlock.ItemsCollection.CollectionChanged += textBlock.ResetInlines;
textBlock.Inlines.Clear();
foreach (ISection lCurr in textBlock.ItemsCollection.Items)
{
textBlock.Inlines.Add(lCurr.GetDisplayElement());
}
}
else
{
inlines = new SectionList();
inlines.Add(new TextOption("No value set"));
textBlock.ItemsCollection = inlines;
}
}
})));
public SectionList ItemsCollection
{
get
{
return (SectionList)GetValue(ItemsCollectionProperty);
}
set
{
SectionList lTemp;
if (value == null)
{
lTemp = new SectionList();
lTemp.Add(new TextOption("No value set for property"));
}
else
{
lTemp = value;
}
SetValue(ItemsCollectionProperty, lTemp);
}
}
private void ResetInlines()
{
Inlines.Clear();
foreach (ISection lCurr in ItemsCollection.Items)
{
Inlines.Add(lCurr.GetDisplayElement());
}
}
}
And I update the fields that were Binded to be of type MultiTypeDynamicTextBlock.SectionList
As long as I am using a copy (Clone) it is working, for some reason when I don't clone it removes the value from the display in the list, if someone knows why I would love to learn but I managed to go around it.
the XAML of the window is:
<StackPanel>
<ListBox x:Name="TheList" ItemsSource="{Binding GeneralItems}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock ItemsCollection="{Binding Items}" Margin="20,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding GeneralItems, Path=SelectedItem}">
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock DataContext="{Binding Items}" ItemsCollection="{Binding}" Margin="20,0,0,0"/>
</StackPanel>
</StackPanel>

Dynamically add textboxes and update binding as it happens

First this is just for testing purposes
I have a xaml page that contains only a button and 2 stackpanels
The idea is to have a class, the class will have codes and names associated to it. the codes will be queried and based on the result a appropriate number of textboxes will display. I need to be able at the end of it add names to textboxes and save the list.
Here is my current code, it creates the textboxes the way I want it to but not too sure on saving of the list once they are filled in
xaml
<StackPanel x:Name="StackSG" Grid.Row="1" Grid.Column="0">
<ListBox ItemsSource="{Binding StackSG}" />
</StackPanel>
<StackPanel x:Name="StackSGName" Grid.Row="1" Grid.Column="1">
<ListBox ItemsSource="{Binding StackSGName}" />
</StackPanel>
<Button x:Name="Generate" Content="Generate" Height="23" Width="75" Grid.Row="0" Grid.Column="0"
Command="{Binding Path=GenerateSGKeys}"/>
</Grid>
</UserControl>
It connect to my viewmodel which looks like this
public class stackpnl : Notify
{
private IEnumerable stackSG;
public IEnumerable StackSG
{
get { return stackSG; }
set { stackSG = value; OnNotifyPropertyChanged("StackSG"); }
}
private IEnumerable stackSGName;
public IEnumerable StackSGName
{
get { return stackSGName; }
set { stackSGName = value; OnNotifyPropertyChanged("StackSGName"); }
}
private DelegateCommand generateSGKeys;
public ICommand GenerateSGKeys
{
get { return generateSGKeys; }
}
private IEnumerable allotments;
public IEnumerable Allotments
{
get { return allotments; }
set { allotments = value; OnNotifyPropertyChanged("Allotments"); }
}
TextBox txtSGName = new TextBox();
public stackpnl()
{
generateSGKeys = new DelegateCommand(Generate, OnGenerate);
StackSG = new List<TextBox>();
StackSGName = new List<TextBox>();
}
private bool OnGenerate(object obj)
{
return true;
}
public class Allotment
{
#region Properties
public string AllotmentName { get; set; }
public string AllotmentCode { get; set; }
#endregion
}
private void Generate(object obj)
{
IList<TextBox> StackSGTmp = new List<TextBox>();
IList<TextBox> StackSGNameTmp = new List<TextBox>();
IList<Allotment> newList = new List<Allotment>();
int st = 10;
for (int i = 0; i < st; i++)
{
newList.Add(new Allotment() { AllotmentCode = string.Format("{0}{1}", "Code", i.ToString()), AllotmentName = string.Format("{0}{1}", "Code", i.ToString()) });
}
foreach (var Allotment in newList)
{
TextBox txtSG = new TextBox();
txtSG.Name = string.Format(Allotment.AllotmentCode);
txtSG.Height = 25;
txtSG.Width = 75;
txtSG.Text = string.Format(Allotment.AllotmentCode);
txtSG.Visibility = System.Windows.Visibility.Visible;
StackSGTmp.Add(txtSG);
//Add SG name textboxes
txtSGName = new TextBox();
txtSGName.Name = string.Format(string.Format("{0}{1}", Allotment.AllotmentCode, Allotment.AllotmentName));
txtSGName.Height = 25;
txtSGName.Width = 75;
txtSGName.SetBinding(TextBox.TextProperty, new Binding(Allotment.AllotmentName) { Mode = BindingMode.TwoWay });
txtSGName.Visibility = System.Windows.Visibility.Visible;
txtSGName.KeyDown += new KeyEventHandler(txtSGName_KeyDown);
StackSGNameTmp.Add(txtSGName);
}
StackSG = StackSGTmp;
StackSGName = StackSGNameTmp;
}
void txtSGName_KeyDown(object sender, KeyEventArgs e)
{
BindingExpression binding = (sender as TextBox).GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
}
}
What you need to do is use the ItemTemplate of the ListBox to display the appropriate content. Your ListBox should not bind to a collection of TextBoxes, but instead bind to a collection of Allotments.
Update the xaml to
<ListBox ItemsSource="{Binding Allotments}" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
<TextBox Text="{Binding AllotmentCode}"/>
<TextBox Text="{Binding AllotmentName}" Grid.Column="2"/>
</DataTemplate
</ListBox.ItemTemplate>
</ListBox>
Notice I removed the StackPanel you were using to wrap the ListBox as it is not needed. In your code you would then remove the StackSG, and StackSGName properties and populate your Allotments property within the Generate method.

Getting checkbox checked items content in a string array in C#

I have a listbox containing checkboxes. I want to get all the checkbox checked items content in a string array. How can I get this?
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox x:Name="TrackingView1" Margin="9,0,2,5">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="fileNameLinkButton" Content="{Binding BindsDirectlyToSource=True}" FontFamily="Segoe WP Semibold" Foreground="Black" FontSize="20" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
You can use VisualTreeHelper to retrieve the datatemplate items,
use this method to get the first item of datatemplate
//method for finding first element of the listbox data template
private T FindFirstElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject
{
var count = VisualTreeHelper.GetChildrenCount(parentElement);
if (count == 0)
return null;
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parentElement, i);
if (child != null && child is T)
{ return (T)child; }
else
{
var result = FindFirstElementInVisualTree<T>(child);
if (result != null)
return result;
}
}
return null;
}
then use this code in the event you want to, for example on a button click
int itemsCount = this.TrackingView1.Items.Count;
List<string> myList = new List<string>();
for (int i = 0; i < itemsCount; i++)
{
ListBoxItem item = (ListBoxItem)this.TrackingView1.ItemContainerGenerator.ContainerFromIndex(i);
CheckBox tagregCheckBox = FindFirstElementInVisualTree<CheckBox>(item);
if((bool)tagregCheckBox.IsChecked)
myList.Add(tagregCheckBox.Content.ToString());
}
In a classic TextBook approach, You could bind "TrackingView1" to a IList, where PersonClass looks some thing like
public class PersonClass
{
public string Name { get; set; }
public bool IsSelected { get; set; }
public PersonClass(string name, bool isSelected)
{
this.Name = name;
this.IsSelected = isSelected;
}
}
and at the point where you want to collect your data,
string[] checkedNames = (from name in Names
where name.IsSelected
select name.Name).ToArray();
I would actually avoid the ToArray(), and accessing the UI directly

Categories