I want to create a new Pivot Item through C# during runtime, displaying a list Bx of the type ListBoxWithCheckBoxes from the toolkit, facilitating to toggle checkboxes visible or invisible in the left side.
My current version works, as far as drawing the new pivot Page, and binding items to it. But i can't get the ListBoxWithCheckBoxes to work properly.
This is from my cs file:
var itemTemplate =
#"<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
<StackPanel Margin=""0,0,0,17"" HorizontalAlignment=""Stretch"" Height=""78"" Orientation=""Vertical"">
<TextBlock Text=""{Binding Title}"" TextWrapping=""Wrap"" Style=""{StaticResource PhoneTextExtraLargeStyle}"" Width=""Auto""/>
<TextBlock Text=""{Binding Description}"" TextWrapping=""Wrap"" Margin=""12,-6,12,0"" Style=""{StaticResource PhoneTextSubtleStyle}"" Width=""Auto""/>
</StackPanel>
</DataTemplate>";
//Creating Pivot Item
PivotItem newPiv = new PivotItem();
newPiv.Header = "Pivot Header"; //defining a header
//Content for the Pivot Item
ListBoxWithCheckBoxes newList = new ListBoxWithCheckBoxes(); //new listbox
newList.ItemsSource = App.ViewModel.Items; //Grapping some items
newList.ItemTemplate = (DataTemplate)XamlReader.Load(itemTemplate); //using xaml template
//Adding the list to the Pivot Item
newPiv.Content = newList; //Adding list to Pivot Item
MainItemList.Items.Add(newPiv); //Adding Pivot Item
Additional info:
I suspect it to have something to do with namespaces. on the XAML, this is added:
xmlns:my="clr-namespace:System.Windows.Controls;assembly=WindowsPhoneListBoxWithCheckBoxesControl"
And a normal ListBoxWithCheckBoxes, which is not made via c# in runtime, works fine. this is made this way:
<my:ListBoxWithCheckBoxes x:Name="FancyListBox" Margin="0,0,-12,0" HorizontalAlignment="Stretch" ItemsSource="{Binding Items}" >
Register for Loaded event on MyPivotItem and set "IsInChooseState" to "true" in the event handler
private void MyPivotItem_Loaded(object sender, RoutedEventArgs e)
{
MyPivotItem pivotItem = sender as MyPivotItem;
pivotItem.myListBox.IsInChooseState = true;
}
Related
I've got a thorny problem and I'm hoping you can help. I'm creating a datagrid which involves dynamically creating columns. Here's some pseudocode for my classes:
GameLibrary
ObservableCollection<Game> Games
Game
ObservableCollection<CustomField> CustomFields
Customfield
ObservableCollection<string> Values
The datagrid is bound to a CollectionViewSource that uses GameLibrary.Games as its Source. The datagrid displays the other properties from Game in each row as I've set up the columns, and then I've got it dynamically created a column for each CustomField in CustomFields and display the relevant CustomField's Values in an itemscontrol in the cell.
This all works great, no problem. Now, though, I'm wanting to sort the Values alphabetically to display. I know best practice for this is using a CollectionViewSource, and I have managed to get one set up, attached to the DataTemplate and displaying in the itemscontrol - but it only works if, as a test, I set the CVS's source to be something external to the datagrid. This displays, but of course it displays the same thing in every row.
How do I bind the DataTemplate's CVS to something in the current row of the table? It's easy enough when not using the CVS, because I can use the binding's Path and just say "CustomFields[i].Values", but I don't know how that translates across to the CVS Source.
Here's what I have now, which works great:
FrameworkElementFactory listbox = new FrameworkElementFactory(typeof(ItemsControl));
Binding b = new Binding();
string pathb = "CustomFields[" + i + "].Values";
b.Path = new PropertyPath(pathb);
b.Mode = BindingMode.TwoWay;
b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
listbox.SetBinding(ItemsControl.ItemsSourceProperty, b);
listbox.SetValue(ItemsControl.PaddingProperty, new Thickness(5));
dock.AppendChild(listbox);
DataTemplate dt = new DataTemplate { VisualTree = dock };
dt.Seal();
newcolumn.CellTemplate = dt;
gameDataDisplay.Columns.Add(newcolumn);
And here's what I want:
DataTemplate dt = new DataTemplate { VisualTree = dock };
CollectionViewSource listboxCVS = new CollectionViewSource();
SortDescription listboxsortDescription = new SortDescription(".", ListSortDirection.Ascending);
listboxCVS.SortDescriptions.Add(listboxsortDescription);
listboxCVS.Source = SOMETHING HERE BUT I DONT KNOW WHAT;
dt.Resources.Add("customCVS" + i, listboxCVS);
FrameworkElementFactory listbox = new FrameworkElementFactory(typeof(ItemsControl));
Binding b = new Binding();
b.Source = listboxCVS;
listbox.SetBinding(ItemsControl.ItemsSourceProperty, b);
listbox.SetValue(ItemsControl.PaddingProperty, new Thickness(5));
dock.AppendChild(listbox);
dt.Seal();
newcolumn.CellTemplate = dt;
gameDataDisplay.Columns.Add(newcolumn);
I've also tried instead of using a CVS binding to a property in CustomFields that returns a sorted list of the Values and that displays fine, but I know it's not best practice and it doesn't update until you scroll the item offscreen and back, so I think that's a dead end.
Thank you for any help you can offer,
Tom.
PS: The ObservableCollections here aren't strictly ObservableCollections, they're a derived class with a couple extra methods, but they act exactly the same for all practical purposes. Just mentioning here for completeness.
I solved this a different way. Rather than creating a new datatemplate programmatically for each custom column, I defined a datatemplate in the window's resources then used a ContentPresenter to fill in the bindings.
XAML:
<DataTemplate x:Key="customfieldtemplate">
<DockPanel x:Name="customfieldHolder" VerticalAlignment="Center">
<DockPanel.Resources>
<CollectionViewSource x:Key="customfieldview" x:Name="customfieldview" Source="{Binding Values}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="."/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<CollectionViewSource x:Key="customfieldsview" x:Name="customfieldsview" Source="{Binding PossibleValues}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="."/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</DockPanel.Resources>
<customcontrols:MultiComboBox Width="17" DockPanel.Dock="Right" Margin="5,0" ShowText="False" x:Name="customfieldMiniCombo" ItemsSource="{Binding Source={StaticResource customfieldsview}}" SelectedItems="{Binding Values}" SelectionMode="Multiple" BorderBrush="{DynamicResource MahApps.Brushes.TextBox.Border}" Background="{DynamicResource MahApps.Brushes.ThemeBackground}" Foreground="{DynamicResource MahApps.Brushes.Text}"/>
<ItemsControl ItemsSource="{Binding Source={StaticResource customfieldview}}" Padding="5"/>
</DockPanel>
</DataTemplate>
Codebehind:
for (int i = 0; i < maindatafile.CurrentGameLibrary.CustomFields.Count; i++)
{
CustomColumn newcolumn = new CustomColumn();
newcolumn.Header = maindatafile.CurrentGameLibrary.CustomFields[i].Name;
gameDataDisplay.Columns.Add(newcolumn);
var template = FindResource("customfieldtemplate");
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(ContentPresenter));
factory.SetValue(ContentPresenter.ContentTemplateProperty, template);
Binding newBinding = new Binding("CustomFields[" + i + "]");
newBinding.Mode = BindingMode.TwoWay;
newBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
factory.SetBinding(ContentPresenter.ContentProperty, newBinding);
DataTemplate dt = new DataTemplate { VisualTree = factory };
newcolumn.CellTemplate = dt;
}
The multicombobox user control (found here) in the template contains an "allowed" set of values to add to or remove from the observablecollection. Its ItemsSource is another property in CustomField, whose getter returns the list of allowable values for that CustomField. The multicombobox's SelectedItems property uses the same binding as the ItemsControl.
End result? The cell displays the list of values in the appropriate CustomField, with a lil dropdown button next to it. That dropdown contains all the possible values for that field, with the ones currently in the list selected. The list updates live as you select and deselect values in this combobox, and also updates live when those values are changed in other parts of the program.
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 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 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.
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.