DataTemplate + MVVM - wpf

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);
}
}

Related

binding string format from code-behind?

Please, can some body tell me how to get my double value formatted like "0.0" from code-behind, like this:
Binding b = new Binding(DoubleValue);
b.StringFormat = "????";
In xaml it works just like that "0.0"...
What about this?
b.StringFormat = "{0:F1}";
See the documentation of StringFormat and also Standard Numeric Format Strings and Custom Numeric Format Strings.
EDIT: Just to make clear how a binding would be created and assigned (to the Text property of an imaginary TextBlock named textBlock) in code:
public class ViewModel
{
public double DoubleValue { get; set; }
}
...
var viewModel = new ViewModel
{
DoubleValue = Math.PI
};
var binding = new Binding
{
Source = viewModel,
Path = new PropertyPath("DoubleValue"),
StringFormat = "{0:F1}"
};
textBlock.SetBinding(TextBlock.TextProperty, binding);
Alternatively:
var binding = new Binding
{
Path = new PropertyPath("DoubleValue"),
StringFormat = "{0:F1}"
};
textBlock.DataContext = viewModel;
textBlock.SetBinding(TextBlock.TextProperty, binding);

How can you set the DataContext dynamically in C#-code?

I builded a RibbonGroupBox like this in a C# file:
public class TextControl : RibbonGroupBox
{
public TextControl()
{
const double widthOfComboBoxes = 150;
Binding fontsBinding = new Binding();
fontsBinding.Source = (TextControlVM)DataContext;
fontsBinding.Path = new System.Windows.PropertyPath("Fonts");
fontsBinding.Mode = BindingMode.TwoWay;
Binding fontSizeBinding = new Binding();
fontSizeBinding.Source = (TextControlVM)DataContext;
fontSizeBinding.Path = new System.Windows.PropertyPath("FontSize");
fontSizeBinding.Mode = BindingMode.TwoWay;
/* Combobox for the fonts (Arial, etc.) */
Fluent.ComboBox fontCombo = new Fluent.ComboBox();
fontCombo.SetBinding(Fluent.ComboBox.ItemsSourceProperty, fontsBinding);
fontCombo.SelectedItem = ((TextControlVM)DataContext).DefaultFont;
fontCombo.Width = widthOfComboBoxes;
this.AddChild(fontCombo);
/* Combobox for the fontsizes */
Fluent.ComboBox fontSizeCombo = new Fluent.ComboBox();
fontSizeCombo.SetBinding(Fluent.ComboBox.ItemsSourceProperty, fontSizeBinding);
fontSizeCombo.SelectedItem = ((TextControlVM)DataContext).DefaultFontSize;
fontSizeCombo.Width = widthOfComboBoxes;
this.AddChild(fontSizeCombo);
}
}
I furthermore have a viewmodel (TextControlVM) that contains Properties for Fonts, FontSize, DefaultFont and DefaultFontSize.
When I now use this in another module like this, the DataContext in the above example is null:
<Fluent:RibbonTabItem Header="Export">
<TextControl DataContext="{Binding DataContext.TextControl}"/>
</Fluent:RibbonTabItem>
When I build the RibbonGroupBox with XAML code everything works fine, so I want to do what XAML automatically does. How can I do that?
Background: I want to use the RibbonGroupBox in several modules. That is why I build it with C#-Code, so that I can access it dynamically. The DataContext will change dependend on the call.
The DataContext is implied in a binding automatically, so you are essentially binding to RibbonTabItem.DataContext.DataContext.TextControl, which doesn't exist
To bind to RibbonTabItem.DataContext.TextControl, simply leave the extra DataContext out of the binding
<Fluent:RibbonTabItem Header="Export">
<TextControl DataContext="{Binding TextControl}"/>
</Fluent:RibbonTabItem>

Adding new columns at runtime

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);

How to bind a boolean value to checkboxcolumn datagrid WPF

i'm working on a project at a company for school and I have to make a datagrid with ticketinformation: The id-number, description of a ticket.
In my Tickettable in the database, I have a column 'Item' and it can have the values: "Not to be invoiced","To be invoiced" or "Offer". The 3rd column in the datagrid has to be a checkboxcolumn and my boss wants me that there is a possibility to check the checkbox, that means the ticket has to be invoiced, and when I uncheck, it means "not to be invoiced" or "offer", that doesn't really matter. My problem is when I bind the "ID" and "Description"-fields, I don't know how to bind the checkboxcolumn with a 'true or false' field? I only know how to bind tablefields from Ticket, but I want to add a boolean-field so I can determine in code when it's "to be invoiced", my boolean-field has to be true and when it's not to be invoiced, it has to be false, so unchecked.
dgTickets.DataContext = new List<ISSUE>();
dgTickets.Columns.Add(new DataGridTextColumn { Header = "Id", Binding = new Binding("IM_ISSUE_NO") });
dgTickets.Columns.Add(new DataGridTextColumn { Header = "Description", Binding = new Binding("IM_DESCRIPTION") });
DataGridCheckBoxColumn chk = new DataGridCheckBoxColumn();
chk.Header = "To be invoiced?";
List<ISSUE> lTickets = new List<ISSUE>();
lTickets = _ISSUEBO.getTickets();
//here I want to make a list of booleans when the tickets are 'to be invoiced or not' in the database
List<bool> lChecks = new List<bool>();
int intTeller = 0;
bool boolFact = false;
foreach (ISSUE i in lTickets) {
switch (i.IM_ITEM_CODE) {
case "TO BE INVOICED":
boolFact = true;
break;
case "NOT TB INVOICED":
boolFact = false;
break;
case "OFFER":
boolFact = false;
break;
default: break;
}
lChecks.Add(boolFact);
intTeller++;
}
Binding b = new Binding("lChecks"); //??? this is probably wrong, but i don't know how to do
chk.Binding = b;
this.dgTickets.Columns.Add(chk);
dgTickets.ItemsSource = lTickets;
Can anyone help me please?
Thanks in advance
I don't really understand your need for the boolean list.
I'd use a converter and bind directly to the property. Something like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataGrid dgTickets = new DataGrid();
ObservableCollection<ISSUE> Issues = new ObservableCollection<ISSUE>(); // better an ObservableCollection than a List here, so that it updates correctly when you modify the content.
dgTickets.DataContext = Issues;
dgTickets.Columns.Add(new DataGridTextColumn { Header = "Id", Binding = new Binding("IM_ISSUE_NO") });
dgTickets.Columns.Add(new DataGridTextColumn { Header = "Description", Binding = new Binding("IM_DESCRIPTION") });
dgTickets.Columns.Add(new DataGridCheckBoxColumn {
Header = "To be invoiced?",
Binding = new Binding("IM_ITEM_CODE") { Converter = new ItemCodeToBoolConverter() }
});
}
}
/// <summary>
/// converts the item code string into a boolean
/// </summary>
public class ItemCodeToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string itemCode = (string)value;
return (itemCode == "TO BE INVOICED");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
bool toBeInvoiced = (bool)value;
return toBeInvoiced ? "TO BE INVOICED" : "NOT TB INVOICED OR OFFER";
}
}
In addition to the previous answer, I'd advise you to use more XAML. WPF's biggest advantage is that you can easily have independant view & model (thanks to the MVVM pattern)
In this case, you have a DataGrid which is displaying the content of a custom object, am I right?
First of all, are you familiar with data binding and OnPropertyChanged interface? If not you should definitely try to know more about that, which is really easy to use.
The way I'd do that is to add in your ViewModel a property "list", containing a list of the objects to be displayed.
Then, you can just declare your datagrid and bind it to the list:
<DataGrid ItemsSource="{Binding List}" >
And you can define templates for what is in the datagrid. In this case:
<DataGrid.Columns>
<!-- definition of the previous columns -->
<DataGridCheckBoxColumn>
<!-- Details on your checkbox here -->
</DataGridCheckBoxColumn>
</DataGrid.Columns>
If you bind the checkBox to your boolean, it will be directly checked or not, according to the boolean value :-)

How to get Binding value of current cell in a WPFToolkit DataGrid

I want to change the foreground color of cells that hold negative numbers, but I don't know how to specify the DataTrigger that would let me. I'm using something like this:
<Style x:Key="NumberCellStyle" BasedOn="{StaticResource CellStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResourceExtension SignConverter}}" Value="-1">
<Setter Property="TextBlock.Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
But in the SignConverter converter I get the whole ViewModel instead of the numeric value I want to convert. I want this to work across the app, without me needing to specify the correct Path for each binding.
Thank you very much!
Better way, write a custom column.
The code follows for anyone that's in the same situation:
public class DataGridDecimalColumn : DataGridTextColumn
{
Binding foregroundBinding;
DecimalBrushConverter brushConverter = new DecimalBrushConverter {
NegativeBrush = Brushes.Red,
PositiveBrush = Brushes.Black,
ZeroBrush = Brushes.Black,
};
protected override FrameworkElement
GenerateElement(DataGridCell cell, object dataItem)
{
var element = base.GenerateElement(cell, dataItem) as TextBlock;
element.SetBinding(TextBlock.ForegroundProperty, GetForegroundBinding());
return element;
}
Binding
GetForegroundBinding()
{
if(foregroundBinding == null) {
var binding = (Binding)Binding;
foregroundBinding = new Binding {
Path = binding.Path,
Converter = BrushConverter,
};
}
return foregroundBinding;
}
public DecimalBrushConverter
BrushConverter
{
get { return brushConverter; }
set { brushConverter = value; }
}
}
DecimalBrushConverter simple takes a decimal? and converts it to one of the specified brushes depending on its value.
What control are you applying the style too? It sounds like whatever you are applying it to doesn't have any specific bindings set for itself, so it is just inheriting its parents' value, which ends up being your ViewModel instance.
Update: Based on the comment, I think that you need to specify a Path in the Binding expression of the style. Since no path is specified, it just uses the current DataContext, which ends up being the entire ViewModel instance.
OK, I didn't find a way to solve my original problem, but I'll work around it by using a DataGridTemplateColumn with templates that correctly set the Foreground color depending on the value that's bind to them.
How would you get this code to read the IsSelected property of the DataGrid itself? I've tried the following code but can't work out how to get the bool value into the ConverterParameter, where the DecimalBrushConverter reads the parameter and provides the SelectedBrush if isSelected==true.
public class DataGridDecimalColumn : DataGridTextColumn
{
private readonly DecimalBrushConverter _brushConverter = new DecimalBrushConverter
{
NegativeBrush = Brushes.Red,
PositiveBrush = Brushes.Black,
ZeroBrush = Brushes.Black,
SelectedBrush = Brushes.White
};
private Binding _foregroundBinding;
private DecimalBrushConverter BrushConverter
{
get { return _brushConverter; }
}
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var element = base.GenerateElement(cell, dataItem) as TextBlock;
if (element != null)
element.SetBinding(TextBlock.ForegroundProperty, GetForegroundBinding());
return element;
}
private Binding GetForegroundBinding()
{
if (_foregroundBinding == null)
{
var binding = (Binding) Binding;
var bindingToRow = new Binding
{
Path = new PropertyPath("IsSelected"),
RelativeSource=new RelativeSource(RelativeSourceMode.FindAncestor,typeof(DataGridRow),1)
};
_foregroundBinding = new Binding
{
Path = binding.Path,
Converter = BrushConverter,
ConverterParameter = bindingToRow
};
}
return _foregroundBinding;
}
}

Resources