I have a button, when pressed it adds a textbox and a listbox to a stackpanel and adds this stackpanel to another stackpanel named "stackPanelAdd". Just like this:
private void buttonAdd_Click(object sender, RoutedEventArgs e)
{
StackPanel sp = new StackPanel();
TextBox tb = new TextBox();
ListBox lb = new ListBox();
tb.Margin = new Thickness(5, 5, 5, 0);
lb.Margin = new Thickness(5, 5, 5, 0);
lb.Height = 200;
sp.Children.Add(tb);
sp.Children.Add(lb);
stackPanelAdd.Children.Add(sp);
}
How do I remove the last children in the stackpanel "stackPanelAdd"?
Should I use something like stackPanelAdd.children.Remove? if so then how do i get the last element in the stackpanel?
Try:
if (stackPanelAdd.Children.Count>0)
{
stackPanelAdd.Children.RemoveAt(stackPanelAdd.Children.Count-1);
}
That is not a good idea, if you stick to this method things will probably get very messy sooner or later. When dealing with items that can be added and removed in WPF you will want to use an ItemsControl of some kind on top of panels (you can change the panel using the ItemsPanel property, by default it will be a StackPanel).
The creation of the controls can also be improved by using data templates and data binding which are core mechanisms that you should become familiar with.
An example:
<ItemsControl ItemsSource="{Binding Data}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Name}" Margin="5,5,5,0"/>
<ListBox ItemsSource="{Binding Items}" Margin="5,5,5,0" Height="200"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here Data is a source collection which should implement INotifyCollectionChanged, then you can just remove an item from that collection and its corresponding StackPanel will be gone. The items in Data should contain the bound properties Name and Items which you then can assign values to or get entered text from (the class should implement INPC, read more about those things in the article on data binding).
You can use
var lastControl = stackPanelAdd.Children.LastOrDefault();
//Last is defined in System.Linq.Enumrable
if(lastControl != null)
stackPanelAdd.Children.Remove(lastControl);
#Milan Halada's answer worked for me with a little change,
while (stackPanelAdd.Children.Count>0)
{
stackPanelAdd.Children.RemoveAt(stackPanelAdd.Children.Count-1);
}
so, it removes all the children and then i add new children to it dynamically using for loop, with new data.
Related
I have a Pivot where I set the header in my Pivot.HeaderTemplate it is basically just showing Names of Books. In my Pivot.ItemTemplate I want to show a Grid which is build in my .xaml.cs but since the Grid is in my DataTemplate I can not access the Grid x:Name anymore in the code behind in .xaml.cs. books is a Collection of Books which contains a Name and a Title
MainPage.xaml
<Pivot ItemsSource="{x:Bind books}">
<Pivot.HeaderTemplate>
<DataTemplate x:DataType="local:Book">
<TextBlock Text="{x:Bind Name}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<Grid
x:Name="BooksGrid"
BorderBrush="Black" BorderThickness="1 1 0 0"
Margin="0 10 0 0>
</Grid>
</DataTemplate>
</Pivot.ItemTemplate>
Now I want to acces BooksGrid iny the code behind and actually create the Grid
MainPage.xaml.cs
public MainPage()
{
this.InitializeComponent();
}
private void DrawGrid()
{
//create columns of Grid
for (int i = 0; i < booksize.XProperties.Count + 1; i++)
{
BooksGrid.ColumnDefinitions.Add(new ColumnDefinition
{
});
}
BooksGrid.ColumnDefinitions[0].Width = GridLength.Auto;
}
....
Already here at BooksGrid.ColumnDefinitions.Add(...) I get the error that BooksGrid can not be found.
My DrawGrid works if I do not place the Grid definition in my DataTemplate and also outside myPivot. So the MainPage.xaml.csdoes not find it when the Grid is inside my DataTemplate
I've read that the solution might be that I have to acces the Grid instance that I want to work with, as soon as the DataTemplate gets loaded. But I do not know how to do that either.
EDIT PART to first solution:
I'm also using BooksGrid in another method
MainPage.xaml.cs
private void DrawBooksFront(Front front)
{
int row;
int column;
column = booksize.XProperties.IndexOf(front.CustomProps[booksize.XLabel])+1;
row = booksize.YProperties.IndexOf(front.CustomProps[booksize.YLabel])+1;
Frame newFrame = new Frame();
TaskBoardGrid.Children.Add(newFrame);
Grid.SetColumn(newFrame, column);
Grid.SetRow(newFrame, row);
}
The reason you cannot access your BooksGrid is because it will be dynamically generated for each book in the books collection. So for every book a Grid will be generated.
OPTION 1:
You can add a Loaded event to your grid:
<Pivot x:Name="Pivot" ItemsSource="{x:Bind books}">
<Pivot.HeaderTemplate>
<DataTemplate x:DataType="local:Book">
<TextBlock Text="{x:Bind Name}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<Grid
BorderBrush="Black" BorderThickness="1,1,0,0"
Margin="0,10,0,0" Loaded="DrawGrid">
</Grid>
</DataTemplate>
</Pivot.ItemTemplate>
and in your code behind:
private void DrawGrid(object sender, RoutedEventArgs e)
{
Grid grid = sender as Grid;
// Load your grid..
}
EDIT - OPTION 2:
If you'd like to access your grids from code behind in a different way (like suggested in your edit) you can always do the following:
private void DrawBooksFront(Front front)
{
// Loop through the pivot's items and get the content from each item's ContentTemplate.
foreach (var item in Pivot.Items)
{
PivotItem pivotItem = Pivot.ContainerFromItem(item) as PivotItem;
Grid grid = pivotItem.ContentTemplate.LoadContent() as Grid;
// Do something with the grid.
}
}
If your goal is to display previews of the pages of the book inside a PivotItem in a grid-like manner [picture below], then you're better off placing GridView in a DataTemplate of Pivot.ItemTemplate and using data binding to display those pages automatically, this would eliminate the need to write the code in xaml.cs that you showed.
Please, share more details about your app (what you're given and what the end result should look like) so we could help you better.
I have a class called TabViewModel, and it has properties like Name, etc..
I need to be able to add tabs dynamically, and whenever a new tab is added, I need create a new instance of the TabViewModel and bind it to the new tab.
Here's my code:
XAML:
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
Code behind:
When adding a new tab..
_tabItems = new List<TabItem>();
tabItem.DataContext = ViewModel.CreateNewTabViewModel();
_tabItems.Add(tabItem);
TabControl1.ItemsSource = _tabItems;
TabControl1.SelectedIndex = 0;
So, CreateNewTabViewModel is suppose to create a new TabViewModel and set the Name property to be displayed on the tab header, which is why the TextBlock is bounded to Name.
I also tried tabItem.SetBinding but it didn't work.
Please advice.
Thanks!
_tabItems = new List<TabItem>();
//...
_tabItems.Add(tabItem);
TabControl1.ItemsSource = _tabItems;
Replaces the entire list of tab items with a new list that contains just a single tab item.
That said, the code is not quite clear on what it is doing, a lot seem unneeded. This works:
var tabItems = new List<TabViewModel>();
tabItems.Add(new TabViewModel { Name = "MyFirstTab" });
myTabControl.ItemsSource = tabItems;
myTabControl.SelectedIndex = 0;
All you need to do is add an instance of a view model to a list of view models and point the tab control to use it. There is no need to set the data context; by setting the items source you are implicitly setting the datacontext of each tab to an item in the collection.
I am trying to change the foreground color of List View from code behind but i am getting object reference not set to an instance of object exception. Here is my Code;
var item = listViewTest.SelectedItem;
ListViewItem listViewItem = this.listViewTest.ContainerFromItem(item) as ListViewItem;
listViewItem.Foreground = new SolidColorBrush(Colors.GreenYellow);
//manually scrolling to the selected item
listViewTest.ScrollIntoView(listViewTest.SelectedItem);
As you can see from the code, what i want is to change the foreground color to e.g yellow and then scroll to that particular listview item. The scrolling works but the foreground color isn't working and i am getting exception.
Update
Here is the item template;
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,9.5">
<TextBlock
FontFamily="Times New Roman"
Text="{Binding Id}"
HorizontalAlignment="Right"
Pivot.SlideInAnimationGroup="1"
CommonNavigationTransitionInfo.IsStaggerElement="True"
Style="{StaticResource ListViewItemTextBlockBlackStyle}"/>
<TextBlock
Text="{Binding FullInfo}"
HorizontalAlignment="Right"
Pivot.SlideInAnimationGroup="2"
CommonNavigationTransitionInfo.IsStaggerElement="True"
Style="{StaticResource ListViewItemSubheaderTextBlockBlackStyle}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
Update 2
Here is the debugger which shows ContainerFromItem null
The reason why listViewTest.ContainerFromItem(item) is returning null is because
Container not rendered yet
No item found
Container Item is not visible in listview yet (maybe you need to scroll to see that item)
Solution
Before you call listViewTest.ScrollIntoView(listViewTest.SelectedItem);
call await System.Threading.Tasks.Task.Delay(1); to let listview to load first. Then only call scrollToView()
Another solution is to add the item yourself so you can access the container by listViewTest.Items[listViewTest.SelectedIndex] and set the forecolor there
Edits
To add item manually just loop trough your item and call this method.
private void AddItem(Foo f)
{
ListViewItem lvi = new ListViewItem();
StackPanel sp = new StackPanel();
TextBlock tb_id = new TextBlock();
tb_id.Text = f.Id;
// Set your other proerty here
sp.Children.Add(tb_id);
TextBlock tb_fullInfo = new TextBlock();
tb_fullInfo.Text = f.FullInfo;
// Set your other property here
sp.Children.Add(tb_fullInfo);
lvi.Content = sp;
listViewTest.Items.Add(lvi);
}
And of course you need to set your other properties like font family and such.
You could use the following code to change foreground color of selected item
private void connecteditems_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListViewItem item = (sender as ListView).SelectedItem;
item.Foreground = new SolidColorBrush(Colors.Red);
}
I am building a small Windows Phone application which has a databound ListBox as a main control. DataTemplate of that ListBox is a databound ItemsControl element, which shows when a person taps on a ListBox element.
Currently, I am accessing it by traversing the visual tree of the application and referencing it in a list, and than getting the selected item through SelectedIndex property.
Is there a better or more effective way?
This one works currently, but I am afraid if it would stay effective in case of larger lists.
Thanks
Have you tried wiring the SelectionChanged event of the ListBox?
<ListBox ItemsSource="{Binding}" SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- ... -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
With this in the code behind:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox listBox = sender as ListBox;
// nothing selected? ignore
if (listBox.SelectedIndex != -1)
{
// something is selected
}
// unselect the item so if they press it again, it takes the selection
listBox.SelectedIndex = -1;
}
ListBoxItem item = this.lstItems.ItemContainerGenerator.ContainerFromIndex(yourIndex) as ListBoxItem;
Then you can use the VisualTreeHelper class to get the sub items
var containerBorder = VisualTreeHelper.GetChild(item, 0) as Border;
var contentControl = VisualTreeHelper.GetChild(containerBorder, 0);
var contentPresenter = VisualTreeHelper.GetChild(contentControl, 0);
var stackPanel = VisualTreeHelper.GetChild(contentPresenter, 0) as StackPanel; // Here the UIElement root type of your item template, say a stack panel for example.
var lblLineOne = stackPanel.Children[0] as TextBlock; // Child of stack panel
lblLineOne.Text = "Some Text"; // Updating the text.
Another option is to use services of the GestureServices class available in the WP7 Toolkit.
You'll need to add a GestureListner to the Root Element of your DataTemplate like so:
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Controls:GestureService.GestureListener>
<Controls:GestureListener Tap="GestureListener_Tap" />
</Controls:GestureService.GestureListener>
<TextBlock x:Name="lblLineOne" Text="{Binding LineOne}" />
<TextBlock Text="{Binding LineTwo}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
And in the GestureListener_Tap event handler, you use this snippet.
private void GestureListener_Tap(object sender, GestureEventArgs e)
{
var itemTemplateRoot = sender as StackPanel;
var lbl1 = itemTemplateRoot.Children[0] as TextBlock;
MessageBox.Show(lbl1.Text);
}
I'm not sure how the GestureListner recognize internally the item being tapped but I guess that it uses the VisualTreeHelper, at least this method is more concise.
I'm unable to figure out how to select an item programmatically in a ListView.
I'm attempting to use the listview's ItemContainerGenerator, but it just doesn't seem to work. For example, obj is null after the following operation:
//VariableList is derived from BindingList
m_VariableList = getVariableList();
lstVariable_Selected.ItemsSource = m_VariableList;
var obj =
lstVariable_Selected.ItemContainerGenerator.ContainerFromItem(m_VariableList[0]);
I've tried (based on suggestions seen here and other places) to use the ItemContainerGenerator's StatusChanged event, but to no avail. The event never fires. For example:
m_VariableList = getVariableList();
lstVariable_Selected.ItemContainerGenerator.StatusChanged += new EventHandler(ItemContainerGenerator_StatusChanged);
lstVariable_Selected.ItemsSource = m_VariableList;
...
void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
//This code never gets called
var obj = lstVariable_Selected.ItemContainerGenerator.ContainerFromItem(m_VariableList[0]);
}
The crux of this whole thing is that I simply want to pre-select a few of the items in my ListView.
In the interest of not leaving anything out, the ListView uses some templating and Drag/Drop functionality, so I'm including the XAML here. Essentially, this template makes each item a textbox with some text - and when any item is selected, the checkbox is checked. And each item also gets a little glyph underneath it to insert new items (and this all works fine):
<DataTemplate x:Key="ItemDataTemplate_Variable">
<StackPanel>
<CheckBox x:Name="checkbox"
Content="{Binding Path=ListBoxDisplayName}"
IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}, Path=IsSelected}" />
<Image ToolTip="Insert Custom Variable" Source="..\..\Resources\Arrow_Right.gif"
HorizontalAlignment="Left"
MouseLeftButtonDown="OnInsertCustomVariable"
Cursor="Hand" Margin="1, 0, 0, 2" Uid="{Binding Path=CmiOrder}" />
</StackPanel>
</DataTemplate>
...
<ListView Name="lstVariable_All" MinWidth="300" Margin="5"
SelectionMode="Multiple"
ItemTemplate="{StaticResource ItemDataTemplate_Variable}"
SelectionChanged="lstVariable_All_SelectionChanged"
wpfui:DragDropHelper.IsDropTarget="True"
wpfui:DragDropHelper.IsDragSource="True"
wpfui:DragDropHelper.DragDropTemplate="{StaticResource ItemDataTemplate_Variable}"
wpfui:DragDropHelper.ItemDropped="OnItemDropped"/>
So what am I missing? How do I programmatically select one or more of the items in the ListView?
Bind the IsSelected property of the ListViewItem to a property on your model. Then, you need only work with your model rather than worrying about the intricacies of the UI, which includes potential hazards around container virtualization.
For example:
<ListView>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsGroovy}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Now, just work with your model's IsGroovy property to select/deselect items in the ListView.
Where 'this' is the ListView instance. This will not only change the selection, but also set the focus on the newly selected item.
private void MoveSelection(int level)
{
var newIndex = this.SelectedIndex + level;
if (newIndex >= 0 && newIndex < this.Items.Count)
{
this.SelectedItem = this.Items[newIndex];
this.UpdateLayout();
((ListViewItem)this.ItemContainerGenerator.ContainerFromIndex(newIndex)).Focus();
}
}
Here would be my best guess, which would be a much simpler method for selection. Since I'm not sure what you're selecting on, here's a generic example:
var indices = new List<int>();
for(int i = 0; i < lstVariable_All.Items.Count; i++)
{
// If this item meets our selection criteria
if( lstVariable_All.Items[i].Text.Contains("foo") )
indices.Add(i);
}
// Reset the selection and add the new items.
lstVariable_All.SelectedIndices.Clear();
foreach(int index in indices)
{
lstVariable_All.SelectedIndices.Add(index);
}
What I'm used to seeing is a settable SelectedItem, but I see you can't set or add to this, but hopefully this method works as a replacement.
In case you are not working with Bindings, this could also be a solution, just find the items in the source and add them to the SelectedItems property of your listview:
lstRoomLights.ItemsSource = RoomLights;
var selectedItems = RoomLights.Where(rl => rl.Name.Contains("foo")).ToList();
selectedItems.ForEach(i => lstRoomLights.SelectedItems.Add(i));