I have a dynamically generated ListView, that uses data binding to allow editing some Boolean values through CheckBox. I use a IValueConverter to generate the ListView's columns (like in this answer):
public object Convert (object Value, Type EntryType, object Parameter, System.Globalization.CultureInfo Culture)
{
var Config = Value as ColumnConfig;
if (Config != null)
{
var GridView = new GridView ();
Binding NameBinding = new Binding ("Name");
GridViewColumn BaseColumn = new GridViewColumn { Header = "Settings",
DisplayMemberBinding = NameBinding,
Width = 125,
CellTemplate = new DataTemplate ()};
GridView.Columns.Add (BaseColumn);
foreach (Column CurrentColumn in Config.Columns)
{
Binding NewBinding = new Binding (CurrentColumn.DataField);
FrameworkElementFactory FEF = new FrameworkElementFactory (typeof (CheckBox));
FEF.SetBinding (CheckBox.IsCheckedProperty, NewBinding);
GridViewColumn GVColumn = new GridViewColumn
{
Header = CurrentColumn.Header,
DisplayMemberBinding = NewBinding
};
var DTemplate = new DataTemplate ();
DTemplate.VisualTree = FEF;
GVColumn.CellTemplate = DTemplate;
GridView.Columns.Add (GVColumn);
}
return GridView;
}
return Binding.DoNothing;
}
Which is used like so in the XAML:
<ListView Margin="2" ItemContainerStyle="{StaticResource LineHighlightListView}"
ItemsSource="{Binding InMatrixList}"
View="{Binding InMatrixColumns, Converter={StaticResource ConvertItemsToDynamicGridView}}" />
The columns' headers are generated elsewhere. The code should take in a ColumnConfig items, and create GridViewColumn objects that have a ChechBox databound to some other value elsewhere. However, all I am getting is columns with text in place of the CheckBoxes. The text is correct, so the data binding is valid, but the FrameworkElementFactory object is not working as expected.
Why are the checkboxes rendered/converted to textboxes?
Rule: avoid that way to dynamically compose a template.
I had a similar problem and I have solved as follows:
//see: http://www.codeproject.com/Articles/444371/Creating-WPF-Data-Templates-in-Code-The-Right-Way
private static DataTemplate CreateTemplate(UniprogCellVM cell)
{
var tcell = cell.GetType();
var sb = new StringBuilder();
sb.AppendFormat("<DataTemplate DataType=\"{{x:Type local:{0}}}\">", tcell.Name);
sb.Append("<local:UniprogCellControl ");
sb.Append("Content=\"{Binding Path=.}\" ");
sb.Append("Header=\"{Binding Path=.}\" ");
sb.AppendFormat("Style=\"{{DynamicResource Root{0}BoxStyleKey}}\" ", cell.Interaction);
sb.Append(">");
sb.Append("</local:UniprogCellControl>");
sb.Append("</DataTemplate>");
var context = new ParserContext();
context.XamlTypeMapper = new XamlTypeMapper(new string[0]);
context.XamlTypeMapper.AddMappingProcessingInstruction("local", tcell.Namespace, tcell.Assembly.FullName);
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
context.XmlnsDictionary.Add("local", "local");
var template = (DataTemplate)XamlReader.Parse(sb.ToString(), context);
return template;
}
Basically, you should compose a fully-valid XAML of your template, then parse it with the parser.
Since the text-composition is a trivial task, you may pass any parameter in the creation function (as in my example above).
Just a final note: this approach is useful, but requires a computational effort because the runtime parsing and compilation. Avoid a large number of items created in such way.
Related
I am trying to center a column in a ListView in the code-behind. Since the columns are dynamically created during runtime, there is no option to do this in the XAML.
Here the helper I created. Setting the other properties like font color works fine - any ideas?
private void ListViewHelper(GridView gridView, ListView listView, MatrixId assignment, string key, IValueConverter converter)
{
// new column
GridViewColumn gridViewColumn = new GridViewColumn();
// header text and formating
gridViewColumn.Header = new TextBlock { Text = assignment.Id.ToString(), TextAlignment = TextAlignment.Center, Padding = new Thickness(7, 0, 0, 0), Width = 50 };
// databinding & converter
FrameworkElementFactory frameworkElementFactory = new FrameworkElementFactory(typeof(TextBlock));
DataTemplate dataTemplate = new DataTemplate();
dataTemplate.VisualTree = frameworkElementFactory;
gridViewColumn.CellTemplate = dataTemplate;
Binding binding = new Binding(assignment.Id.ToString() + key);
binding.Converter = converter;
// *** this does not work ***
frameworkElementFactory.SetValue(TextBlock.TextAlignmentProperty, TextAlignment.Right);
frameworkElementFactory.SetValue(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Right);
frameworkElementFactory.SetValue(TextBlock.ForegroundProperty, new SolidColorBrush(Color.FromRgb(100, 100, 100)));
frameworkElementFactory.SetBinding(TextBlock.TextProperty, binding);
// add column
gridView.Columns.Add(gridViewColumn);
}
If you want to center the content in a GridView inside a ListView, you can do it by creating an item container style for ListViewItem and set the HorizontalContentAlignment to Center.
var itemContainerStyle = new Style(typeof(ListViewItem));
var horizontalContentAlignmentSetter = new Setter(HorizontalContentAlignmentProperty, HorizontalAlignment.Center);
itemContainerStyle.Setters.Add(horizontalContentAlignmentSetter);
listView.ItemContainerStyle = itemContainerStyle;
Here, listView is your list view instance. The alignment will apply to all columns. If you need to align columns individually, set the HorizontalContentAlignment in the item container template to Stretch.
var itemContainerStyle = new Style(typeof(ListViewItem));
var horizontalContentAlignmentSetter = new Setter(HorizontalContentAlignmentProperty, HorizontalAlignment.Stretch);
itemContainerStyle.Setters.Add(horizontalContentAlignmentSetter);
listView.ItemContainerStyle = itemContainerStyle;
Then set the HorizontalAlignment of the controls inside the CellTemplate to what you need.
private void ListViewHelper(GridView gridView, ListView listView, MatrixId assignment, string key, IValueConverter converter)
{
// ...your code.
// Set the alignment to "Left", "Center", "Right" or "Stretch"
frameworkElementFactory.SetValue(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Right);
// ...your code.
}
I have the following coding where I bind a CheckBox and TextBlock into one DataGridTemplateColumn.
Would it be possible for me to edit the cell with the checkbox and textbox when I click on the cell itself to edit the text inside of it? I still want to be able to set my CheckBox to true or false at the same time as editing the text within the textblock.
Here is my coding:
private void btnFeedbackSelectSupplier_Click(object sender, RoutedEventArgs e)
{
DataGridTemplateColumn columnFeedbackSupplier = new DataGridTemplateColumn();
columnFeedbackSupplier.Header = "Supplier";
columnFeedbackSupplier.CanUserReorder = true;
columnFeedbackSupplier.CanUserResize = true;
columnFeedbackSupplier.IsReadOnly = false;
//My stack panel where I will host the two elements
var stackPanel = new FrameworkElementFactory(typeof(StackPanel));
stackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
DataTemplate cellTemplate = new DataTemplate();
//Where I create my checkbox
FrameworkElementFactory factoryCheck = new FrameworkElementFactory(typeof(CheckBox));
Binding bindCheck = new Binding("TrueFalse");
bindCheck.Mode = BindingMode.TwoWay;
factoryCheck.SetValue(CheckBox.IsCheckedProperty, bindCheck);
stackPanel.AppendChild(factoryCheck);
//Where I create my textblock
FrameworkElementFactory factoryText = new FrameworkElementFactory(typeof(TextBlock));
Binding bindText = new Binding("Supplier");
bindText.Mode = BindingMode.TwoWay;
factoryText.SetValue(TextBlock.TextProperty, bindText);
stackPanel.AppendChild(factoryText);
cellTemplate.VisualTree = stackPanel;
columnFeedbackSupplier.CellTemplate = cellTemplate;
DataGridTextColumn columnFeedbackSupplierItem = new DataGridTextColumn();
columnFeedbackSupplier.Header = (cmbFeedbackSelectSupplier.SelectedItem as DisplayItems).Name;
dgFeedbackAddCost.SelectAll();
IList list = dgFeedbackAddCost.SelectedItems as IList;
IEnumerable<ViewQuoteItemList> items = list.Cast<ViewQuoteItemList>();
var collection = (from i in items
let a = new ViewQuoteItemList { Item = i.Item, Supplier = i.Cost, TrueFalse = false }
select a).ToList();
dgFeedbackSelectSupplier.Columns.Add(columnFeedbackSupplier);
dgFeedbackSelectSupplier.ItemsSource = collection;
}
My example of how it looks now and how I would like to edit that R12 value inside the cell, while still being able to set the checkbox to true or false.
As for my original question, YES you can edit the cell with a CheckBox inside, but instead of a TextBlock I used a TextBox and I changed my the following coding from my question:
var stackPanel = new FrameworkElementFactory(typeof(StackPanel));
stackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);//Delete this line
To
var dockPanel = new FrameworkElementFactory(typeof(DockPanel));
Because a StackPanel does not have support for certain elements (like a TextBox) to fill the remaining available space, where a DockPanel does have support for it.
And then I added this line to make my TextBox fill the remaining space availble
factoryText.SetValue(TextBox.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
Hope this will help someone else out there :)
Why do you want to use TextBlock instead of TextBox ? If you want to expand the full width of my column length, then just set HorizontalAlignment to Stretch like that:
FrameworkElementFactory factoryText = new FrameworkElementFactory(typeof(TextBox));
factoryText.Text = HorizontalAlignment.Stretch;
Update:
And put your TextBox into Grid or DockPanel; as Zach Johnson says that StackPanel is meant for "stacking" things even outside the visible region, so it won't allow you to fill remaining space in the stacking dimension.
I have a ListBox and I want to change the appearance of each ListBoxItem based on the index of the previous ListBoxItem in the ListBox.
In order to do that I have created a CustomListBoxItem which inherits from ListBoxItem and have set a Style for it.
Setters.Add(new Setter(ListBoxItem.ContentTemplateProperty, new CustomListBoxItemContentTemplate());
In the constructor of CustomListBoxItemContentTemplate class which inherits from DataTemplate I have a DataTrigger as follows:
DataTrigger dt = new DataTrigger();
dt.Binding = new Binding()
{
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(CustomListBoxItem), 1),
Mode = BindingMode.OneWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Converter = new CustomConverter()
};
dt.Value = true;
Triggers.Add(dt);
And finally in the Convert method of the CustomConverter class I have implemented the IValueConverter as follows:
var item = value as CustomBoxItem;
if (item == null)
return false;
/// I get the ItemsControl here
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
/// and the index of the CustomListBoxItem
int index = ic.ItemContainerGenerator.IndexFromContainer(item);
if (index == 0)
return true;
var previousItem = ic.ItemContainerGenerator.ContainerFromIndex(index - 1) as CustomListBoxItem;
/// ....
I should say every thing works perfectly fine in the first place when the ItemsSource is bound to the source collection but when I add or remove items to the CustomListBox, the DataTrigger does not reevaluate the whole items. it does only for the added items.
I really don't know what the problem is, or maybe my approach is not very good to do the job.
Any help is appreciated. Thanks a lot.
I have been trying to create a ContentTemplate using the lovely Frameworkelementfactory.
The code works except I can't set the content of the Button. I have tried many things but I always end up with a Button with Content= Button.
Here is the code which generates the contenttemplate. For your further info, I am using this in a Tabcontrol Header Itemtemplate...
Cheers.
ControlTemplate ct = new ControlTemplate(typeof(TabItem));
FrameworkElementFactory spouter = new FrameworkElementFactory(typeof (DockPanel));
FrameworkElementFactory text = new FrameworkElementFactory(typeof(TextBlock));
text.SetValue(TextBlock.TextProperty, Name);
spouter.AppendChild(text);
FrameworkElementFactory mButtonPrev = new FrameworkElementFactory(typeof(Button));
mButtonPrev.SetValue(System.Windows.Controls.Button.ContentProperty, "x");
mButtonPrev.AddHandler(System.Windows.Controls.Button.ClickEvent, new RoutedEventHandler(CloseTab));
spouter.AppendChild(mButtonPrev);
ct.VisualTree = spouter;
return ct;
ControlTemplate ct = new ControlTemplate(typeof(TabItem));
Shouldn't you be creating a DataTemplate here instead?
(Everything else looks fine to me, also FEF is deprecated, read the docs)
For those of use still using FEF, I was able to set the content of my button with the actual string. I see Name as where "button" is coming from in your example. In my example, Name pulls up the class name that I'm binding to my datagrid.
var buttonTemplate = new FrameworkElementFactory(typeof(Button));
var text = new FrameworkElementFactory(typeof(TextBlock));
text.SetValue(TextBlock.TextProperty, "Save");
buttonTemplate.AppendChild(text);
buttonTemplate.AddHandler(
System.Windows.Controls.Primitives.ButtonBase.ClickEvent,
new RoutedEventHandler((o, e) => MessageBox.Show("hi"))
);
AccountDatagrid.Columns.Add(new DataGridTemplateColumn()
{
Header = "Save",
CellTemplate = new DataTemplate() { VisualTree = buttonTemplate }
});
AccountDatagrid.ItemsSource = AccoutDescCodeTime.GetBaseAccounts();
I have a Datagrid which show data from a database.
each time i load the data from my data vase the old data is earsed and the is replaced with the new data.
What i want is for the pervious row to remain and the new data to be appened to the end of the datagrid.
my code is as shown below :
public MainPage()
{
InitializeComponent();
dataGrid1.Columns.Add(new DataGridTextColumn { Header = "Year", Binding = new System.Windows.Data.Binding("year") });
dataGrid1.Columns.Add(new DataGridTextColumn { Header = "One", Binding = new System.Windows.Data.Binding("One") });
dataGrid1.Columns.Add(new DataGridTextColumn { Header = "Two", Binding = new System.Windows.Data.Binding("Two") });
dataGrid1.Columns.Add(new DataGridTextColumn { Header = "Three", Binding = new System.Windows.Data.Binding("Three") });
}
void client_DoWorkCompleted(object sender, DoWorkCompletedEventArgs e)
{
dataGrid1.ItemsSource = e.Result;
}
how do i append the new data from the database rather than overwriting the data ?? .
One very simple and no-frills way to do it (because you don't appear to be using a regular MVVM approach), is to create a new list (IEnumerable) which unions both the old list and the new list, then assign it back to the ItemsSource property:
List<MyObjects> abc = new List<MyObjects>(dataGrid2.ItemsSource).Union(e.Result);
dataGrid2.ItemsSource = abc;
I've broken this on to two lines to make it easier to understand.
If you were using a ViewModel, then the property that is bound to the DataGrid would be a List<>, and then you can just do an AddRange(e.Result) to that property, and due to the beauty of data binding (and notifying) it will automatically show up in your datagrid.