I'm trying to create a custom CheckBox control which will ideally be represented by a blue cross, which turns a lighter shade of blue when the mouse Mouse-Over event occurs, and fires a Click event when clicked.
I've seen ways of doing this in the XAML code using Control Templates and Styles, but not for purely in the code behind. I've created custom Styles in the code before and applied them well enough, but I'm having trouble with the amount of customization required this time around e.g replacing the whole checkbox with an image of a blue cross.
Does anybody know a standard way of a doing this? Can you create a full templated style in the XAML code, and then reference that template when setting the Properties on your new checkbox object in the code behind?
Thanks in advance.
I hate to do this, as it ruins the declaration approach with XAML, but there are times that you need to do it.
That being said, take a look at the FrameworkElementFactory class to build your XAML. It's a pretty neat pattern. The following snippet shows where I created a DataTemplate for a ListView in code. I needed to dynamically add elements based on the number of days in a month for a time reporting application.
GridView gv = new GridView();
int i = 0;
foreach (string s in vm.DateList)
{
string column = string.Format("DisplayTime[{0}].Hours", i);
DataTemplate dt = new DataTemplate();
DateTime date = DateTime.Parse(s);
bool isWeekday = true;
if (date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday)
{
isWeekday = false;
}
Binding binding = new Binding();
binding.Path = new PropertyPath(column);
binding.Mode = BindingMode.TwoWay;
FrameworkElementFactory gridElement = new FrameworkElementFactory(typeof(Grid));
gridElement.SetValue(Grid.WidthProperty, 60.0);
gridElement.SetValue(Grid.HeightProperty, 94.0);
gridElement.SetValue(Grid.MarginProperty, new Thickness(0.0, 0.0, 0.0, 4.0));
if (!isWeekday)
{
gridElement.SetValue(Grid.BackgroundProperty, new SolidColorBrush(Color.FromRgb(65, 65, 65)));
}
FrameworkElementFactory txtelement = new FrameworkElementFactory(typeof(TextBox));
txtelement.SetBinding(TextBox.TextProperty, binding);
txtelement.SetValue(TextBox.WidthProperty, 40.0);
txtelement.SetValue(TextBox.HeightProperty, 20.0);
txtelement.SetValue(TextBox.VerticalAlignmentProperty, VerticalAlignment.Center);
txtelement.SetValue(TextBox.HorizontalAlignmentProperty, HorizontalAlignment.Center);
txtelement.SetValue(TextBox.TextAlignmentProperty, TextAlignment.Right);
gridElement.AppendChild(txtelement);
dt.VisualTree = gridElement;
gv.Columns.Add(new GridViewColumn()
{
Header = s,
// DisplayMemberBinding = new Binding(column),
CellTemplate = dt
});
i++;
}
ETCListView.View = gv;
Related
I have a datagrid with a DataGridTemplateColumn containing a CheckBox. I create the column like this:
DataGridTemplateColumn cTemplateColumn = new DataGridTemplateColumn();
cTemplateColumn.Header = "Auswahl";
FrameworkElementFactory cFactory = new FrameworkElementFactory(typeof(CheckBox));
Binding b1 = new Binding("[__intern_cv__]");
//b1.IsAsync = true;
b1.Converter = new StringToBoolConverter(this);
b1.Mode = BindingMode.TwoWay;
b1.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
cFactory.SetValue(CheckBox.IsCheckedProperty, b1);
cFactory.SetValue(CheckBox.HorizontalAlignmentProperty, HorizontalAlignment.Center);
cFactory.AddHandler(CheckBox.CheckedEvent, new RoutedEventHandler(CheckedEvent));
cFactory.AddHandler(CheckBox.UncheckedEvent, new RoutedEventHandler(CheckedEvent));
cFactory.AddHandler(CheckBox.PreviewMouseDownEvent, new MouseButtonEventHandler(checkBoxMouseDown));
DataTemplate cCellTemplate = new DataTemplate();
cCellTemplate.VisualTree = cFactory;
cTemplateColumn.CellTemplate = cCellTemplate;
Columns.Add(cTemplateColumn);
Now i have the problem, that during scrolling throught the DataGrid, the CheckedEvent is called, with Checked == False.
The vents a subscripted here:
cFactory.AddHandler(CheckBox.CheckedEvent, new RoutedEventHandler(CheckedEvent));
cFactory.AddHandler(CheckBox.UncheckedEvent, new RoutedEventHandler(CheckedEvent));
How can scrolling call the event?
maybe someone has an idea, thank you!
By default the DataGrid virtualizes its rows. This means that during scrolling old rows are re-used with new data. If the new data has "[__intern_cv__]" set to false, while the previous data had it set to true an Unchecked event will be raised.
I have a WPF DataGrid written in XAML that I'm converting to C# (don't ask).
It looks something like this (some properties omitted for brevity):
var Card = new DataGrid() {
Background = Brushes.LightYellow,
BorderBrush = Brushes.DimGray,
ColumnWidth = new DataGridLength(100),
Columns = {
new DataGridTextColumn() {
Binding = new Binding("In"),
Header = "In"
},
new DataGridTextColumn() {
Binding = new Binding("Out"),
Header = "Out"
},
new DataGridTextColumn() {
Binding = new Binding("Hours"),
Header = "Hours"
}
},
RowHeaderTemplate = new DataTemplate(typeof(DataGridRowHeader)) {
VisualTree = Days
},
RowHeaderWidth = 115,
RowHeight = 50
};
Days is setup like so:
var Days = new FrameworkElementFactory(typeof(TextBlock));
Days.SetBinding(TextBlock.TextProperty, new Binding("Day"));
Days.SetValue(TextBlock.BackgroundProperty, Brushes.Lime);
When run, the DataGrid's RowHeader is blank (and LightYellow, not Lime).
I've tried Card.RowHeaderTemplate.VisualTree = Days; also, to no avail.
Where am I going wrong? How can I set the RowHeaderTemplate programmatically?
Templates should be created using loading from XAML. Using element factories is obsolete and no longer supported (it may work in some cases, but won't work in other).
For example, Caliburn.Micro creates default data templates like this:
public static DataTemplate DefaultHeaderTemplate = (DataTemplate)
#if SILVERLIGHT || WinRT
XamlReader.Load(
#else
XamlReader.Parse(
#endif
"<DataTemplate " +
" xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>" +
" <TextBlock Text=\"{Binding DisplayName, Mode=TwoWay}\" />" +
"</DataTemplate>"
);
Another link you may find useful: Creating WPF Data Templates in Code
The Right Way. It includes a sample of creating XAML string using XML and XAML classes, with references to assemblies and stuff.
I´m trying to change the horizontal column alignment of a databound DataGrid depending on the data type (e.g. Int32, float,..).
After searching the web for a simple example for ages I have learned, that DataTriggers via xaml should be the right option to do that. Is that right? If so, how would I implement the trigger?
I am quite new to WPF and have been using WindowsForms in the past. It can´t be that difficult to change the column orientation dependent on the data type? Any help is appreciated!
This may help - Different views / data template based on member variable
Another option is to use DataTemplate selector. Just check this tutorial: http://tech.pro/tutorial/807/wpf-tutorial-how-to-use-a-datatemplateselector
You can handle AutoGeneratingColumn event.
In your xaml add :
<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="True"
AutoGeneratingColumn="DataGrid_OnAutoGeneratingColumn"></DataGrid>
In code-behind:
private void DataGrid_OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyType == typeof (Int32))
{
if (e.Column != null)
{
var dgct = new DataGridTemplateColumn();
var cName = e.Column.Header as string;
var b = new Binding(cName);
var sfactory = new FrameworkElementFactory(typeof(TextBlock));
sfactory.SetValue(TextBlock.TextProperty, b);
sfactory.SetValue(TextBlock.TextAlignmentProperty, TextAlignment.Right);
var cellTemplate = new DataTemplate();
cellTemplate.VisualTree = sfactory;
dgct.CellTemplate = cellTemplate;
dgct.Header = cName;
dgct.SortMemberPath = cName;
e.Column = dgct;
}
}
... *and so on for all your data types*
}
You can check these links:
http://msdn.microsoft.com/en-us/library/cc903950(v=vs.95).aspx
http://mareinsula.wordpress.com/2011/06/06/tips-on-wpf-autogeneratinged-datagrid/
Ok, I´ve solved this from the code behind now. Maybe someone could give me a hint how I could solve this more elegantly using XAML? I´ve been searching the web for hours to find an example thats not too hard for someone who´s new to WPF and just didn´t find anything a was able to implement successfully.
Ok, here´s the code: Having a DataTable as DataSource I add the following:
foreach (DataColumn cc in table.Columns)
{
Type type = cc.DataType;
Style alignStyle = new Style(typeof(Microsoft.Windows.Controls.DataGridCell));
alignStyle.Setters.Add(new Setter(Microsoft.Windows.Controls.DataGridCell.VerticalAlignmentProperty, VerticalAlignment.Center));
var column = new Microsoft.Windows.Controls.DataGridTextColumn
{
Header = cc.ColumnName,
Binding = new Binding(cc.ColumnName)
};
if(type.Name=="Int32"){
alignStyle.Setters.Add(new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Right));
column.Foreground = Brushes.Red;
column.CellStyle = alignStyle;
}
else if (type.Name == "DateTime")
{
alignStyle.Setters.Add(new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Center));
column.Foreground = Brushes.Green;
column.Binding.StringFormat = "{0:dd.MM.yyyy}";
column.CellStyle = alignStyle;
}
else if (type.Name == "String")
{
alignStyle.Setters.Add(new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Left));
column.Foreground = Brushes.Blue;
column.CellStyle = alignStyle;
}
else if (type.Name == "Double")
{
alignStyle.Setters.Add(new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Right));
column.Foreground = Brushes.Brown;
column.Binding.StringFormat = "{0:F3}";
column.CellStyle = alignStyle;
}
grids.Columns.Add(column);
}
I am trying to build a dropdown list for a winform interop, and I am creating the dropdown in code. However, I have a problem getting the data to bind based on the DataTemplate I specify.
What am I missing?
drpCreditCardNumberWpf = new ComboBox();
DataTemplate cardLayout = new DataTemplate {DataType = typeof (CreditCardPayment)};
StackPanel sp = new StackPanel
{
Orientation = System.Windows.Controls.Orientation.Vertical
};
TextBlock cardHolder = new TextBlock {ToolTip = "Card Holder Name"};
cardHolder.SetBinding(TextBlock.TextProperty, "BillToName");
sp.Children.Add(cardHolder);
TextBlock cardNumber = new TextBlock {ToolTip = "Credit Card Number"};
cardNumber.SetBinding(TextBlock.TextProperty, "SafeNumber");
sp.Children.Add(cardNumber);
TextBlock notes = new TextBlock {ToolTip = "Notes"};
notes.SetBinding(TextBlock.TextProperty, "Notes");
sp.Children.Add(notes);
cardLayout.Resources.Add(sp, null);
drpCreditCardNumberWpf.ItemTemplate = cardLayout;
Assuming that you've already set up the ItemsSource etc for drpCreditCardNumberWpf...
//create the data template
DataTemplate cardLayout = new DataTemplate();
cardLayout.DataType = typeof(CreditCardPayment);
//set up the stack panel
FrameworkElementFactory spFactory = new FrameworkElementFactory(typeof(StackPanel));
spFactory.Name = "myComboFactory";
spFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
//set up the card holder textblock
FrameworkElementFactory cardHolder = new FrameworkElementFactory(typeof(TextBlock));
cardHolder.SetBinding(TextBlock.TextProperty, new Binding("BillToName"));
cardHolder.SetValue(TextBlock.ToolTipProperty, "Card Holder Name");
spFactory.AppendChild(cardHolder);
//set up the card number textblock
FrameworkElementFactory cardNumber = new FrameworkElementFactory(typeof(TextBlock));
cardNumber.SetBinding(TextBlock.TextProperty, new Binding("SafeNumber"));
cardNumber.SetValue(TextBlock.ToolTipProperty, "Credit Card Number");
spFactory.AppendChild(cardNumber);
//set up the notes textblock
FrameworkElementFactory notes = new FrameworkElementFactory(typeof(TextBlock));
notes.SetBinding(TextBlock.TextProperty, new Binding("Notes"));
notes.SetValue(TextBlock.ToolTipProperty, "Notes");
spFactory.AppendChild(notes);
//set the visual tree of the data template
cardLayout.VisualTree = spFactory;
//set the item template to be our shiny new data template
drpCreditCardNumberWpf.ItemTemplate = cardLayout;
You can use the same way I have set the ToolTip on the TextBlocks to set other properties such as margins.
The full version
var ms = new MemoryStream(Encoding.UTF8.GetBytes(#"<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
xmlns:c=""clr-namespace:MyApp.Converters;assembly=MyApp"">
<DataTemplate.Resources>
<c:MyConverter x:Key=""MyConverter""/>
</DataTemplate.Resources>
<TextBlock Text=""{Binding ., Converter={StaticResource MyConverter}}""/>
</DataTemplate>"));
var template = (DataTemplate)XamlReader.Load(ms);
var cb = new ComboBox { };
//Set the data template
cb.ItemTemplate = template;
Well, indeed we still have another way, you will really like it if you dislike those FrameworkElementFactory things.
And I think it just makes minor changes to the natural code, that is, declare a UserControl and put your control into it, and then, use just one FrameworkElementFactory to call the UserControl.
Simple demo code (in F#):
let buildView()=StackPanel()
//Build it with natural code
type MyView()=inherit UserControl(Content=buildView())
let factory=FrameworkElementFactory(typeof<MyView>)
let template=DataTemplate(VisualTree=factory)
let list=ItemsControl(ItemsSource=makeData(),ItemTemplate=template)
I have a little problem with a Listview.
I can load it with listview items fine, but when I set the background color it doesn't draw the color all the way to the left side of the row [The listViewItems are loaded with ListViewSubItems to make a grid view, only the first column shows the error]. There is a a narrow strip that doesn't paint. The width of that strip is approximately the same as a row header would be if I had a row header.
If you have a thought on what can be done to make the background draw I'd love to hear it.
Now just to try a new idea, I'm offering a ten vote bounty for the first solution that still has me using this awful construct of a mess of a pseudo grid view. [I love legacy code.]
Edit:
Here is a sample that exhibits the problem.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ListView lv = new ListView();
lv.Dock = System.Windows.Forms.DockStyle.Fill;
lv.FullRowSelect = true;
lv.GridLines = true;
lv.HideSelection = false;
lv.Location = new System.Drawing.Point(0, 0);
lv.TabIndex = 0;
lv.View = System.Windows.Forms.View.Details;
lv.AllowColumnReorder = true;
this.Controls.Add(lv);
lv.MultiSelect = true;
ColumnHeader ch = new ColumnHeader();
ch.Name = "Foo";
ch.Text = "Foo";
ch.Width = 40;
ch.TextAlign = HorizontalAlignment.Left;
lv.Columns.Add(ch);
ColumnHeader ch2 = new ColumnHeader();
ch.Name = "Bar";
ch.Text = "Bar";
ch.Width = 40;
ch.TextAlign = HorizontalAlignment.Left;
lv.Columns.Add(ch2);
lv.BeginUpdate();
for (int i = 0; i < 3; i++)
{
ListViewItem lvi = new ListViewItem("1", "2");
lvi.BackColor = Color.Black;
lvi.ForeColor = Color.White;
lv.Items.Add(lvi);
}
lv.EndUpdate();
}
}
Ah! I see now :}
You want hacky? I present unto you the following:
...
lv.OwnerDraw = true;
lv.DrawItem += new DrawListViewItemEventHandler( lv_DrawItem );
...
void lv_DrawItem( object sender, DrawListViewItemEventArgs e )
{
Rectangle foo = e.Bounds;
foo.Offset( -10, 0 );
e.Graphics.FillRectangle( new SolidBrush( e.Item.BackColor ), foo );
e.DrawDefault = true;
}
For a more inventive - and no less hacky - approach, you could try utilising the background image of the ListView ;)
(Prior to the Edit...)
I've just tried setting the BackColor on a System.Windows.Forms.ListView, and the color is applied across the control just fine (with and without images).
Are you doing any Custom Painting at all?
Ok I'm adding some additional solution notes. If you use the solution above you also need to insert a draw handler for the column headers, otherwise they won't paint. The selected item rectangle also looks funny so you'll want to check for that in the lv_DrawItem function and implement a similar solution. Remeber that highlighting is chosen at the system level and not in you application.
Better ListView (and free Better ListView Express) allows setting background image with various alignment settings (centered, tiled, stretched, fit, snap to border/corner). Alpha transparency is also supported: