Adding new columns at runtime - silverlight

I am working in Silverlight MVVM pattern, there is a datagrid (component one) which initially will be having 4 columns, later at runtime user will be displayed list of columns in combobox, he can select the columns he want to see in the datagrid and click a button to add the columns in datagrid, so at runtime i have to add the Column in the datagrid. Is it possible using the Silverlight MVVM pattern. Please help.

Sure it's possible. You'll define the data grid and binding in your xaml, then you'll have to do data binding and add the columns in code. Be sure and turn autoGenerateColumns off. I keep a separate list that describes my columns. I also define the column template in code, since I want tooltip and headers different for each column. Here is an example that adds either a float or integer column, you can add other types if you need to :
First define the grid - I don't think it matters that you're using a component one datagrid
<!-- NOTE: Creating columns is done Programmatically -->
<sdk:DataGrid x:Name="DataGridForDistrictSummaries" Grid.Column="1"
AutoGenerateColumns="False"
VerticalScrollBarVisibility="Visible"
HorizontalScrollBarVisibility="Visible"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MinHeight="70"
ItemsSource="{Binding dataGridView}"
AlternatingRowBackground="LightBlue"
>
</sdk:DataGrid>
Then you need to build your grid columns, I rebuild the entire grid:
public static void BuildDataGridColumns(DataGrid dataGrid, List<DataGridColumnDescription> columnDescriptionList)
{
// clear out the old columns and re-build them with new criteria
dataGrid.Columns.Clear();
// add columns based on the description
int index = 0;
foreach (DataGridColumnDescription column in columnDescriptionList)
{
if (DataGridColumnDescriptionValueType.floatDataType == column.valueType)
{
dataGrid.Columns.Add(DataGridColumnBuilder.CreateFloatColumn(index++, column.name, column.header, column.description));
}
else
{
dataGrid.Columns.Add(DataGridColumnBuilder.CreateIntColumn(index++, column.name, column.header, column.description));
}
}
}
This lets you have different data types in your grid. That is important for controlling how you want to display the data. In this case, I wanted 2 decimal points to display
public static DataGridTextColumn CreateFloatColumn(int index, string fieldName, string header, string description)
{
DataGridTextColumn column = new DataGridTextColumn();
column.Header = header;
column.HeaderStyle = BuildColumnHeaderStyle(description);
column.Binding = new Binding("floatValuesList[" + index + "]");
column.Binding.StringFormat = "0.00";
column.CellStyle = BuildFloatCellStyle(fieldName, description);
return column;
}
public static DataGridTextColumn CreateIntColumn(int index, string fieldName, string header, string description)
{
DataGridTextColumn column = new DataGridTextColumn();
column.Header = header;
column.HeaderStyle = BuildColumnHeaderStyle(description);
column.Binding = new Binding("intValuesList[" + index + "]");
column.CellStyle = BuildCellStyle(fieldName, description);
return column;
}
This defines a style in code, I've hard coded some things - but you can make it as dynamic as you need:
private static Style BuildColumnHeaderStyle(string tooltip)
{
FontWeight fw = FontWeights.Bold;
Style newGridHeaderStyle = new Style(typeof(DataGridColumnHeader));
newGridHeaderStyle.Setters.Add(new Setter { Property = DataGridColumnHeader.FontSizeProperty, Value = 9.0 });
newGridHeaderStyle.Setters.Add(new Setter { Property = DataGridColumnHeader.FontWeightProperty, Value = FontWeights.Bold });
newGridHeaderStyle.Setters.Add(new Setter { Property = DataGridColumnHeader.ContentTemplateProperty, Value = CreateDataGridColumnHeaderTemplate(tooltip) });
return newGridHeaderStyle;
}
private static Style BuildFloatCellStyle(string fieldName, string tooltip)
{
Style newGridCellStyle = new Style(typeof(DataGridCell));
newGridCellStyle.Setters.Add(new Setter { Property = DataGridCell.FontSizeProperty, Value = 11.0 });
newGridCellStyle.Setters.Add(new Setter { Property = DataGridCell.HorizontalContentAlignmentProperty, Value = HorizontalAlignment.Right });
return newGridCellStyle;
}
private static Style BuildCellStyle(string fieldName, string tooltip)
{
Style newGridCellStyle = new Style(typeof(DataGridCell));
newGridCellStyle.Setters.Add(new Setter { Property = DataGridCell.FontSizeProperty, Value = 11.0 });
newGridCellStyle.Setters.Add(new Setter { Property = DataGridCell.HorizontalContentAlignmentProperty, Value = HorizontalAlignment.Right });
return newGridCellStyle;
}
You have to create the cell template in code as well. This creates the xaml text string and uses a XamlReader to load it
private static DataTemplate CreateDataGridColumnHeaderTemplate(string tooltip)
{
string str = #"<DataTemplate xmlns='http://schemas.microsoft.com/client/2007'>"
+ #"<ContentControl Content='{Binding}'>"
+ #"<ToolTipService.ToolTip>"
+ #"<ToolTip Content='" + tooltip + "'>"
+ #"</ToolTip>"
+ #"</ToolTipService.ToolTip>"
+ #"</ContentControl >"
+ #"</DataTemplate>";
return (DataTemplate)XamlReader.Load(str);
}

Refer link: http://blogs.msdn.com/b/scmorris/archive/2008/04/14/defining-silverlight-datagrid-columns-at-runtime.aspx
Code snippet is from above link:(this is one way of doing that.Otherway is also explained in that link)
Xaml:
<UserControl.Resources>
<local:DateTimeConverter x:Key="DateConverter" />
<DataTemplate x:Key="myCellTemplate">
<TextBlock
Text="{Binding Birthday,
Converter={StaticResource DateConverter}}"
Margin="4"/>
</DataTemplate>
<DataTemplate x:Key="myCellEditingTemplate">
<basics:DatePicker
SelectedDate="{Binding Birthday, Mode=TwoWay}" />
</DataTemplate>
Code Behind:
DataGridTemplateColumn templateColumn = new DataGridTemplateColumn();
templateColumn.Header = "Birthday";
templateColumn.CellTemplate = (DataTemplate)Resources["myCellTemplate"];
templateColumn.CellEditingTemplate =
(DataTemplate)Resources["myCellEditingTemplate"];
targetDataGrid.Columns.Add(templateColumn);

Related

WPF: How do I programmatically style my ListView so that all rows have a certain background color and height?

I can't seem to wrap my head around something. Say I have the following logic in my code:
namespace WPFTesting
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ObservableCollection<Message> messages = new ObservableCollection<Message>();
public MainWindow()
{
InitializeComponent();
messages.Add(new Message(DateTime.Now, "This is a test."));
ListView listView = new ListView();
GridView gridView = new GridView();
listView.View = gridView;
GridViewColumn timeStampColumn = new GridViewColumn();
timeStampColumn.DisplayMemberBinding = new Binding("Date");
GridViewColumnHeader timeStampHeader = new GridViewColumnHeader();
timeStampHeader.Content = "Time";
timeStampColumn.Header = timeStampHeader;
gridView.Columns.Add(timeStampColumn);
GridViewColumn messageColumn = new GridViewColumn();
messageColumn.DisplayMemberBinding = new Binding("Text");
GridViewColumnHeader messageHeader = new GridViewColumnHeader();
messageHeader.Content = "Message";
messageColumn.Header = messageHeader;
gridView.Columns.Add(messageColumn);
Binding binding = new Binding();
binding.Source = messages;
listView.SetBinding(ItemsControl.ItemsSourceProperty, binding);
MainGrid.Children.Add(listView);
}
public class Message
{
public Message(DateTime aDate, String aText)
{
Date = aDate;
Text = aText;
}
public DateTime Date { get; set; }
public String Text { get; set; }
}
}
}
How do I programmatically style my ListView so that all rows have a certain background color and height? Note, I want to avoid doing a foreach loop on the ListView's Items list and setting each ListViewItem's properties, because this list may have very many items and this could be expensive. Instead, is there not some way to do this programmatically using the Style class, or perhaps some run-time logic?
I figured it out. You can do it this way as an example:
Style style = new Style();
style.TargetType = typeof(ListViewItem);
style.Setters.Add(new Setter(ListViewItem.BackgroundProperty, Brushes.Pink));
listView.ItemContainerStyle = style;
Edit: You can also conditionally set a style on the ListView's Items using a trigger for certain values from your data set. I found this very useful, so this may help others as well:
DataTrigger trigger = new DataTrigger();
trigger.Binding = new Binding("Text");
trigger.Value = "This is a test.";
trigger.Setters.Add(new Setter(ListViewItem.BackgroundProperty, Brushes.Pink));
style.Triggers.Add(trigger);
listView.ItemContainerStyle = style;
The above code will only set the background of the row under the condition that the Text field is set to "This is a test."
GridView columns don't support styling, only header styling. Instead use a cell template to style column cells. This allows you to have different styles for different columns. For example you want currency columns to be right aligned but text columns to be left aligned.
This is how it looks using XAML for your Time column with red background style.
<GridViewColumn Header="Time">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Date}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="Red" />
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
This can be done in code. FrameworkElementFactory class is a deprecated class which supports the creation of templates. The recommended way to programmatically create a template is to load XAML from a string or a memory stream using the Load method of the XamlReader class. Below code should be modified to allow for different styles depending on type of column.
public MainWindow()
{
InitializeComponent();
messages.Add(new Message(DateTime.Now, "This is a test."));
ListView listView = new ListView();
GridView gridView = new GridView();
listView.View = gridView;
gridView.Columns.Add(CreateGridViewColumn("Time", "Date"));
gridView.Columns.Add(CreateGridViewColumn("Message","Text"));
listView.SetBinding(ItemsControl.ItemsSourceProperty, new Binding() { Source = messages });
MainGrid.Children.Add(listView);
}
private static GridViewColumn CreateGridViewColumn(string header, string bindingPath)
{
GridViewColumn gridViewColumn = new GridViewColumn();
gridViewColumn.Header = new GridViewColumnHeader() { Content = header };
string xaml = #"
<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
<TextBlock Text=""{Binding " + bindingPath + #"}"">
<TextBlock.Style>
<Style TargetType=""{x:Type TextBlock}"">
<Setter Property=""Background"" Value=""Red"" />
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>";
StringReader stringReader = new StringReader(xaml);
XmlReader xmlReader = XmlReader.Create(stringReader);
gridViewColumn.CellTemplate = XamlReader.Load(xmlReader) as DataTemplate;
return gridViewColumn;
}

Silverlight - DataGrid in Scrollviewer, Column.Width="*" makes datagrid take several screen-widths

When I have the following setup, the last column having a width of * causes the datagrid to create huge horizontal scrollbars (extends grid to several widths of the screen). I'm not really sure why this is, but I really need a way to avoid that. I don't want to have to "simulate" column's with * lengths.
edit: Apparently I'm not the only one who noticed this.
http://connect.microsoft.com/VisualStudio/feedback/details/559644/silverlight-4-datagrid-star-column-width
Xaml:
<ScrollViewer Padding="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" >
<sdk:DataGrid AutoGenerateColumns="False" x:Name="dg"/>
</ScrollViewer>
Code:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
dg.Columns.Add(new DataGridTextColumn { Binding = new Binding("A"), Header = "A" });
dg.Columns.Add(new DataGridTextColumn { Binding = new Binding("B"), Header = "B" });
dg.Columns.Add(new DataGridTextColumn { Binding = new Binding("C"), Header = "C" });
dg.Columns[2].Width = new DataGridLength(1, DataGridLengthUnitType.Star);
dg.ItemsSource = new[]
{
new I { A = "SAF", B = "SAF", C = "SAF" },
new I { A = "SAF", B = "SAF", C = "SAF" },
new I { A = "SAF", B = "SAF", C = "SAF" }
};
}
public class I
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
you need to put a maxwidth on the scrollviewer? otherwise width is defaulted to auto and maxwidth is infinity
If you remove
dg.Columns[2].Width = new DataGridLength(1, DataGridLengthUnitType.Star);
the problem should go away. Can I ask why you want the last column to take the rest of the space? The column actually knows to resize itself properly to fit its cell size and column header size.
Also, if you don't specify the column's width, then you don't really need a scrollviewer to scroll and see all the columns. The datagrid has a scrollviewer built in and when there are columns out of the screen the horizontal scrollbar will apear.
I hope this helps. :)

Listbox carousel - is it possible?

I'm not using a listbox and data binding at the moment, but is it possible to have a listbox work like a carousel and if so, how.
This is what I'm using at the moment, which only works for adding images, not through binding in a listbox... can it still be modified to position each binded canvas+image in the suggested answer?
// add images to the stage
public void addImages()
{
var itemCollection = GalleryModel.DocItemCollection;
foreach (var item in itemCollection)
{
var url = item.ImageUrl;
var image = new Image
{
Source = new BitmapImage(new Uri(url, UriKind.RelativeOrAbsolute))
};
image.Width = 90;
image.Height = 60;
// add the image
LayoutRoot.Children.Add(image);
// Add template here?
// reposition the image
posImage(image, itemCollection.IndexOf(item));
_images.Add(image);
var containingWidth = ActualWidth;
var numberofItemsShown = containingWidth/100;
if (itemCollection.IndexOf(item) < Math.Ceiling(numberofItemsShown)-1)
moveIndex(1);
}
}
// move the index
private void moveIndex(int value)
{
_target += value;
_target = Math.Max(0, _target);
_target = Math.Min(_images.Count - 1, _target);
}
// reposition the image
private void posImage(Image image , int index){
double diffFactor = index - _current;
double left = _xCenter - ((IMAGE_WIDTH + OFFSET_FACTOR) * diffFactor);
double top = _yCenter;
image.SetValue(Canvas.LeftProperty, left);
image.SetValue(Canvas.TopProperty, top);
}
You'd typically use a ListBox for scenarios like this.
The XAML for it would look something like this:
<ListBox x:Name="ImageGalleryListBox">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<tkt:WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding MyImageItemUri}" Margin="8" Width="100" Height="100" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You can of course template it further to make things look the way you want.
In the code-behind here or in your view model, you'd create class that has an MyImageItemUri property and add instances of it to an ObservableCollection<T>. You can then bind or set the collection to the ItemsSource of the ImageGalleryListBox. You'd create more images dynamically by simply adding more of your image items to the observable collection.

Databinding selectedindex + 1

I would like to bind a textbox to show the text of the item below the item that is selected.
Example:
Item 1 - Text = row numer one
Item 2 - Text = row number two
Item 3 - Text = row number three
I select item 2
output
Textbox 1 - Text = row number two (this is easily set up binding to selecteditem)
Textbox 2 - Text = row number three
I was thinking of a custom xpath of selectedindex + 1 but this doesn't seem to work
first attempt failed -- see below
You need to implement an IValueConverter and set it to the Converter attribute of the binding.
Create a class that inherits from IValueConverter, and in the Convert method, you'll cast the value parameter to ListBox (because you'll be binding the TextBox to the ListBox itself and letting the converter turn that into something meaningful).
Then get a reference to the ListBox's SelectedIndex property.
You want to return listBox.Items[selectedIndex + 1] from the method.
You can leave the ConvertBack method unimplemented.
You'll also have to handle the case where the last item in the ListBox is selected, because index + 1 will be out of bounds. Maybe you want to return the first item; maybe you want to return null or string.Empty.
update: custom ListBox
As requested, here is a sample that uses a custom ListBox with an additional [Dependency] property called "ItemAfterSelected."
First, the code for the derived control:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public class PlusOneListBox : ListBox
{
public PlusOneListBox()
{
SelectionMode = SelectionMode.Single;
}
public object ItemAfterSelected
{
get { return GetValue(ItemAfterSelectedProperty); }
set { SetValue(ItemAfterSelectedProperty, value); }
}
public static readonly DependencyProperty ItemAfterSelectedProperty = DependencyProperty.Register(
"ItemAfterSelected", typeof (object), typeof (PlusOneListBox));
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
var newly_selected = e.AddedItems;
if (newly_selected == null) ItemAfterSelected = null;
else
{
var last_index = Items.Count - 1;
var index = Items.IndexOf(newly_selected[0]);
ItemAfterSelected = index < last_index
? Items[index + 1]
: null;
}
base.OnSelectionChanged(e);
}
}
}
Here is a sample window that shows how to use and bind to the control (you can drop this in to an app and run it to see it in action).
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:WpfApplication1" xmlns:System="clr-namespace:System;assembly=mscorlib" Padding="24">
<StackPanel>
<custom:PlusOneListBox x:Name="custom_listbox">
<custom:PlusOneListBox.Items>
<System:String>one</System:String>
<System:String>two</System:String>
<System:String>three</System:String>
<System:String>four</System:String>
<System:String>five</System:String>
<System:String>six</System:String>
</custom:PlusOneListBox.Items>
</custom:PlusOneListBox>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="Selected: " />
<TextBlock Text="{Binding SelectedItem, ElementName=custom_listbox}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="Next: " />
<TextBlock Text="{Binding ItemAfterSelected, ElementName=custom_listbox}" />
</StackPanel>
</StackPanel>
</Window>

DataTemplate + MVVM

I'm using MVVM and each View maps to a ViewModel with a convention. IE
MyApp.Views.MainWindowView
MyApp.ViewModels.MainWindowViewModel
Is there a way to remove the DataTemplate and do it in C#? with some sort of loop?
<DataTemplate DataType="{x:Type vm:MainWindowViewModel}">
<vw:MainWindowView />
</DataTemplate>
So basically, you need to create data templates programmatically... That's not very straightforward, but I think you can achieve that with the FrameworkElementFactory class :
public void AddDataTemplateForView(Type viewType)
{
string viewModelTypeName = viewType.FullName + "Model";
Type viewModelType = Assembly.GetExecutingAssembly().GetType(viewModelTypeName);
DataTemplate template = new DataTemplate
{
DataType = viewModelType,
VisualTree = new FrameworkElementFactory(viewType)
};
this.Resources.Add(viewModelType, template);
}
I didn't test it, so a few adjustments might be necessary... For instance I'm not sure what the type of the resource key should be, since it is usually set implicitly when you set the DataType in XAML
Thanks Thomas, using your code i've done this.
You need to use the DataTemplateKey when adding the resoures :D
private void AddAllResources()
{
Type[] viewModelTypes = Assembly.GetAssembly(typeof(MainWindowViewModel)).GetTypes()
.Where(t => t.Namespace == "MyApp.ViewModels" && t.Name.EndsWith("ViewModel")).ToArray();
string viewName = null;
string viewFullName = null;
foreach (var vmt in viewModelTypes)
{
viewName = vmt.Name.Replace("ViewModel", "View");
viewFullName = String.Format("MyApp.Views.{0}, MyApp", viewName);
DataTemplate template = new DataTemplate
{
DataType = vmt,
VisualTree = new FrameworkElementFactory(Type.GetType(viewFullName, true))
};
this.Resources.Add(new DataTemplateKey(vmt), template);
}
}

Resources