XamlWriter.Save() is not serializing DependencyProperties - c#

Consider the following XAML from my UserControl:
<TextBlock Text="HelloWorld" Loaded="TextBlock_OnLoaded" />
And the associated event handler:
private void TextBlock_OnLoaded(object sender, RoutedEventArgs e)
{
var xaml = XamlWriter.Save(sender);
Console.WriteLine(xaml);
}
When the TextBlock is loaded, the following output is written to the Console:
<TextBlock Text="HelloWorld" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />
Now consider this alternative XAML:
<ListBox ItemsSource="{Binding SomeCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="HelloWorld" Loaded="TextBlock_OnLoaded" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now when the TextBlock is loaded, the following output is written to the Console:
<TextBlock xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />
<TextBlock xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />
<TextBlock xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />
......
Notice that the TextProperty is no longer being serialized.
If the following TextProperty assignment is added before the call to XamlWriter.Save():
private void TextBlock_OnLoaded(object sender, RoutedEventArgs e)
{
var textBlock = sender as TextBlock;
if (textBlock != null)
{
textBlock.Text = textBlock.Text;
}
var xaml = XamlWriter.Save(sender);
Console.WriteLine(xaml);
}
Then when the TextBlock is loaded, the following output is written to the Console:
<TextBlock Text="HelloWorld" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />
<TextBlock Text="HelloWorld" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />
<TextBlock Text="HelloWorld" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />
......
Notice that the TextProperty is again being serialized.
This blog post explains that "... if the property is backed by a DependencyProperty ... the property is only written if it is actually set."
It appears that the TextProperty is indeed being set in the first usage example, but not in the second usage example with the ListBox and DataTemplate.
Can anyone explain why this is the case, and how to overcome this obstacle?
My best guess is that the XAML parser is somehow setting the TextBlock state internally instead of calling SetValue on the dependency property, but I'm not sure why it would do this only for elements inside a DataTemplate.

XamlWriter.Save appears to only serialize locally-set values. In XAML, values can come from multiple levels of sources.
When you set TextBlock.Text directly, you are looking at a "local value" set (precedence 3). However, when you set it inside a data template, you are setting template properties (precedence 4). By writing
textBlock.Text = textBlock.Text;
you are actually transforming that into a local property set (precedence 3)!
If you look at the some of the source code involved in XamlWriter.Save, you can see (line 819) that it explicitly reads the local value of the property.
Unfortunately, I'm not sure what a good work-around is for this. XamlWriter has known limitations. You an try inheriting from XamlDesignerSerializationManager and calling the XamlWriter.Save(Object, XamlDesignerSerializationManager) overload, but it doesn't look very promising. More likely, you will have to either do what you do above, or write your own serialization routine (at least Microsoft has made their source readily available as a guide).

In light of NextInLine's answer, I've come up with the following work around:
public static IEnumerable<DependencyProperty> GetDependencyProperties(this DependencyObject obj)
{
var propertyDescriptors = TypeDescriptor.GetProperties(obj, new Attribute[]
{
new PropertyFilterAttribute(PropertyFilterOptions.All)
});
return (from PropertyDescriptor pd in propertyDescriptors
let dpd = DependencyPropertyDescriptor.FromProperty(pd)
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static IEnumerable<DependencyProperty> GetUpdatedDependencyProperties(this DependencyObject obj)
{
return (from property in obj.GetDependencyProperties().Where(x => !x.ReadOnly)
let metaData = property.GetMetadata(obj.GetType())
let defaultValue = metaData.DefaultValue
let currentValue = obj.GetValue(property)
where currentValue != defaultValue
select property).ToList();
}
Which can be used like this:
foreach (var updatedProperty in dependencyObject.GetUpdatedDependencyProperties())
{
dependencyObject.SetValue(updatedProperty, dependencyObject.GetValue(updatedProperty));
}
This will force XamlWriter.Save(dependencyObject) to serialize all of the dependencyObject properties that were updated in XAML.

Related

How can I modify xaml resource at run-time?

I have many very similar resources in xaml (varying by a tiny bit: name of property in bingings, static text in header, etc.) which are quite big and complex:
<Window.Resource>
<A x:Key="a1"> ... </A>
<A x:Key="a2"> ... </A>
...
<B x:Key="b1"> ... />
<B x:Key="b2"> ... />
...
<C x:Key="c1"> ... />
...
</Window.Resource>
And my aim is to have just this:
<A x:Key="a" ... />
<B x:Key="b" ... />
<C x:Key="c" ... />
...
where resource become kind of template. But then I need to somehow define a parameter to alter each such resource (e.g. to modify property name in the binding) before using it.
My current idea is to manipulate resources as text:
var xaml = #"... Text = ""{Binding %PROPERTY%}"" ...";
xaml = xaml.Replace("%PROPERTY%", realPropertyName);
view.Content = XamlReader.Parse(xaml)
But defining xaml strings in code-behind doesn't sounds good, they should be a part of xaml, where they are used.
So I had this brilliant idea:
// get some resource and restore xaml string for it, yay!
var xaml = XamlWriter.Save(FindResource("some resource"));
But unfortunately XamlWriter is very limited, it didn't worked, the restored this way xaml is totally unusable.
Then I had a thought to define resource as string:
<clr:String x:Key="a">...</clr:String>
But multiline string and special character in xaml making this approach looking very ugly. Don't try it at home.
Ideally I want to define resources as before (to have intellisence and stuff) and just want to modify them at run-time somehow, therefore my question.
The localized case of the problem (it's quite the same) is to have parameter in DataTemplate. I was asking question about dynamic columns earlier, that's why I have so many similar resources defined currently and trying to find a solution again.
I forgot to add a concrete example of resource as well as some form of MCVE:
<Window.Resources>
<GridViewColumn x:Key="column1">
<GridViewColumn.Header>
<DataTemplate>
<TextBlock Text="Header1" />
</DataTemplate>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value1}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
...
... more similar columns
...
</Window.Resources>
<ListView x:Name="listView" ItemsSource="{Binding Items}">
<ListView.View>
<GridView />
</ListView.View>
</ListView>
some columns are added
var view = (GridView)listView.View;
foreach(var column in Columns.Where(o => o.Show))
view.Columns.Add((GridViewColumn)FindResouce(column.Key));
where Columns collection defines which columns can be shown, which are hidden, their width, etc.
public class Column
{
public string Key { get; set; } // e.g. "column1"
public bool Show { get; set; }
...
}
To have 100 columns I have to define 100 "columnX" resources, but they are very similar. My challenge is to define just one and then somehow alter dynamic parts (in this case to change "Header1" to "Header2" and "Value1" to "Value2").
I have found a way to write xaml which:
has designer support
has intellisense support;
can be modified at run-time.
For this xaml (resources) needs to be put into separate ResourceDictionary which Build property set to Embedded Resource
The content will looks like this:
<ResourceDictionary ...>
<GridViewColumn x:Key="test" Header="%HEADER%"> <!-- placeholder for header -->
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding %CELL%}" /> <!-- placeholder for cell property name -->
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</ResourceDictionary>
And the code to load and modify
// get resource stream
var element = XElement.Load(Assembly.GetExecutingAssembly().GetManifestResourceStream("..."));
// get xaml as text for a specified x:Key
var xaml = element.Descendants().First(o => o.Attributes().Any(attribute => attribute.Name.LocalName == "Key")).ToString();
// dynamic part
xaml = xaml.Replace("%HEADER%", "Some header");
xaml = xaml.Replace("%CELL%", "SomePropertyName");
// xaml to object
var context = new ParserContext();
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
var column = (GridViewColumn)XamlReader.Parse(xaml, context);
view.Columns.Add(column);
I personally don't use designer at all (only to quickly navigating) and write all xaml with hands. Having no designer support is not a problem for me.
Intellisense support and seeing mistakes at compile time are very handy. Disregards of build action the xaml will be fully validated, which is a good thing (compared to saving xaml in string in code behind or in a text-file).
Separating resources from window/user control are sometimes problematic, e.g. if there are bindings with ElementName or references to other resources (which are not moved to resource dictionary), etc. I have currently issue with BindingProxy, therefore this solution is not a final one.
More focusing on your GridView example instead of the question title.
You could create a UserControl or Custom Control in order to define the appearence of a cell content.
Within the custom control, you can define your whole shared styling and define dependency properties for things that should be different per cell.
As an example, here is a custom control MyCellContent that allows to bind a Text property or to bind a MyTextPropertyName property which will automatically create a binding on the Text property, redirecting to whatever MyTextPropertyName specifies:
public class MyCellContent : Control
{
static MyCellContent()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCellContent), new FrameworkPropertyMetadata(typeof(MyCellContent)));
}
// Specify a property name that should be used as binding path for Text
public string MyTextPropertyName
{
get { return (string)GetValue(MyTextPropertyNameProperty); }
set { SetValue(MyTextPropertyNameProperty, value); }
}
public static readonly DependencyProperty MyTextPropertyNameProperty =
DependencyProperty.Register("MyTextPropertyName", typeof(string), typeof(MyCellContent), new PropertyMetadata(null, OnTextPropertyChanged));
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BindingOperations.SetBinding(d, TextProperty, new Binding(e.NewValue as string));
}
// The text to be displayed
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyCellContent), new PropertyMetadata(null));
}
And in Themes/Generic.xaml
<Style TargetType="{x:Type local:MyCellContent}">
<!-- Demonstrate the power of custom styling -->
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCellContent}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock Text="{TemplateBinding Text}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, this is something to build upon.
For example, you could create a CellTemplateSelector that creates a cell template, where the Text property binding is determined by another property value of the data item:
// Specialized template selector for MyGenericData items
public class GridViewColumnCellTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var data = item as MyGenericData;
return CreateCellTemplate(data.MyTargetPropertyName);
}
/// <summary>
/// Create a template with specified binding path
/// </summary>
private DataTemplate CreateCellTemplate(string targetPropertyName)
{
FrameworkElementFactory myCellContentFactory = new FrameworkElementFactory(typeof(MyCellContent));
myCellContentFactory.SetValue(MyCellContent.MyTextPropertyNameProperty, targetPropertyName);
return new DataTemplate
{
VisualTree = myCellContentFactory
};
}
}
Another different way to use the MyCellContent control would be a customized MyGridviewColumn, that does basically the same as the template selector above, but instead of a data driven property selection, it allows to specify a binding to be used on the Text property:
/// <summary>
/// Pre-Templated version of the GridViewColumn
/// </summary>
public class MyGridviewColumn : GridViewColumn
{
private BindingBase _textBinding;
public BindingBase TextBinding
{
get { return _textBinding; }
set
{
if (_textBinding != value)
{
_textBinding = value;
CellTemplate = CreateCellTemplate(value);
}
}
}
/// <summary>
/// Create a template with specified binding
/// </summary>
private DataTemplate CreateCellTemplate(BindingBase contentBinding)
{
FrameworkElementFactory myCellContentFactory = new FrameworkElementFactory(typeof(MyCellContent));
myCellContentFactory.SetBinding(MyCellContent.TextProperty, contentBinding);
return new DataTemplate
{
VisualTree = myCellContentFactory
};
}
}
Usage example with some test data:
<Window.Resources>
<x:Array x:Key="testItems" Type="{x:Type local:MyGenericData}">
<local:MyGenericData Property1="Value 1" Property2="Value 3" MyTargetPropertyName="Property1"/>
<local:MyGenericData Property1="Value 2" Property2="Value 4" MyTargetPropertyName="Property2"/>
</x:Array>
<local:GridViewColumnCellTemplateSelector x:Key="cellTemplateSelector"/>
</Window.Resources>
...
<ListView ItemsSource="{Binding Source={StaticResource testItems}}">
<ListView.View>
<GridView>
<GridViewColumn CellTemplateSelector="{StaticResource cellTemplateSelector}" Header="ABC" Width="100" />
<local:MyGridviewColumn TextBinding="{Binding Property2}" Header="DEF" Width="100" />
</GridView>
</ListView.View>
</ListView>
The result:
A gridview where the first column displays the values "Value 1" and "Value 4", because it selects the value from "Property1" in the first row and from "Property2" in the second row. So the displayed data is driven by two data dimensions: the specified property name and the target property value.
The second column displays the values "Value 3" and "Value 4", because it utilizes the specified binding expression "{Binding Property2}". So the displayed data is driven by the specified binding expression, which could refer to a data property or anything else that's legally binding within a data grid cell.

C# Wpf drawing with Combobox

I need help with drawing in my combobox. I want to make a combobox of colors for picking. I found some stuff on the internet but none of them is working. So far I have this :
private void MyComb_DrawItem(object sender, DrawItemEventArgs e)
{
Graphics g = e.Graphics;
System.Drawing.Rectangle rect = e.Bounds;
ColorConverter converter = new ColorConverter();
if (e.Index >= 0)
{
string n = ((ComboBox)sender).Items[e.Index].ToString();
System.Drawing.Color c = (System.Drawing.Color)converter.ConvertFromString(n);
SolidBrush b = new SolidBrush(c);
g.FillRectangle(b, rect.X + 110, rect.Y + 5,
rect.Width - 10, rect.Height - 10);
}
}
This is my drawItem method
<Grid>
<ComboBox x:Name="MyComb" HorizontalAlignment="Left" Margin="66,81,0,0" VerticalAlignment="Top" Width="120" />
</Grid>
This is definition of combobox
Type colorType = typeof(System.Drawing.Color);
PropertyInfo[] propInfoList = colorType.GetProperties(BindingFlags.Static |
BindingFlags.DeclaredOnly | BindingFlags.Public);
foreach (PropertyInfo c in propInfoList)
{
MyComb.Items.Add(c.Name);
}
And here I am filling combobox with colors names and then I want to fill to combox with real colors according to the colors names.. But my draw item method is never called. I tried to create some DrawItem handler but, my combobox have no such thing... Then I read something about setting a DrawMode property of combobox, but my combobox doesn't that kind of property at all...
I am using net framework v.4.6.1
Can please anyone tell me, what am I missing ?
Thank you very much
The biggest problem you're having is that you're trying to use code examples that were written for the Winforms API, even though you are using the WPF API. For future reference, you really need to be more careful about identifying the context of tutorials and other resources you find online, to make sure they actually apply to your scenario.
As it happens, we have a number of related questions on Stack Overflow already:
WPF ComboBox as System.Windows.Media.Colors>
WPF - Bind ComboBox Item Foreground to Its Value
Very simple color picker made of combobox
These are all potentially useful to you, but are all based on the answer to this question:
How can I list colors in WPF with XAML?
Which was originally about just displaying the names of colors, and so took a short-cut, using the <ObjectDataProvider/> element in XAML. This led to the need to use a converter in the other questions, to convert either from a string value or a PropertyInfo instance to the appropriate color or brush.
In fact, if your code is already written to use some type of MVVM approach, and especially since you've already written code-behind to retrieve the color values from the Colors type, or at least tried to (one of the problems in your code is that you are using the Winforms Color type instead of the WPF Colors type…again, in Winforms that works fine, but the WPF API follows the Code Analysis/FxCop rules more closely, and the named colors are in the Colors type), it makes sense to just stick with that and provide a direct view model data structure to which you can bind.
In this approach, rather than providing a procedural implementation of the item drawing, you provide in XAML a declarative implementation describing what each item should look like.
Here is an example…
First, some simple view model data structures:
// Very simple container class
class ColorViewModel
{
public Brush Brush { get; }
public string Name { get; }
public ColorViewModel(Brush brush, string name)
{
Brush = brush;
Name = name;
}
}
// Main view model, acts as the data context for the window
class MainViewModel : INotifyPropertyChanged
{
public IReadOnlyList<ColorViewModel> Colors { get; }
private Brush _selectedColor;
public Brush SelectedColor
{
get { return _selectedColor; }
set { _UpdateField(ref _selectedColor, value); }
}
public MainViewModel()
{
Colors = typeof(Colors).GetProperties(BindingFlags.Static | BindingFlags.Public)
.Select(p => new ColorViewModel(new SolidColorBrush((Color)p.GetValue(null)), p.Name))
.ToList().AsReadOnly();
}
public event PropertyChangedEventHandler PropertyChanged;
private void _UpdateField<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
With those in hand, the XAML is straight-forward:
<Window x:Class="TestSO47850587ColorComboBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO47850587ColorComboBox"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<StackPanel>
<ComboBox Width="100" ItemsSource="{Binding Colors}"
HorizontalAlignment="Left" Grid.IsSharedSizeScope="True"
SelectedValuePath="Brush" SelectedValue="{Binding SelectedColor}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type l:ColorViewModel}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="ComboBoxItem"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" Background="{Binding Brush}" HorizontalAlignment="Stretch"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Rectangle HorizontalAlignment="Stretch" Height="24" Fill="{Binding SelectedColor}"/>
</StackPanel>
</Window>
The above displays the color names, using the actual color as the background. If all you really want is a rectangle, then you can replace the <TextBlock/> element with a <Rectangle/> element, binding to its Fill property. Naturally, you can achieve other visual effects, such as a rectangle with a margin. It's just a matter of configuring your data template according to your need.
The main point here is that you should embrace the data binding approach that defines good WPF programming, and that you should definitely not mistake Winforms code examples for WPF. :)

User Control in Pivot, binding not work

I'm developing windows phone 8 app. I have a customer UserControl called SelectableButton. The constructor of it is as below:
public SelectableButton()
{
InitializeComponent();
DataContext = this;
}
The xaml of it is like this:
<Grid>
<TextBlock x:Name="ButtonTextBlock"
Text="{Binding SelectableButtonText, Mode=TwoWay}"
SomeOtherCode
/>
...
</Grid>
The SelectableButtonText is a property of this UserControl:
public static readonly DependencyProperty SelectableButtonTextProperty =
DependencyProperty.Register(
"SelectableButtonText", typeof(string),
typeof(SelectableButton),
null
);
Now I use this SelectableButton in a Pivot. I want to bind the SelectableButtonText property to some data. This is the DataTemplate used in a Pivot called PivotTestContent:
<ShareControl:SelectableButton
SelectableButtonText="{Binding question}"
...
>
</ShareControl:SelectableButton>
The question is from the ItemsSource of this Pivot:
PivotTestContent.ItemsSource = quizs;
The quizs is a List<> of WCCQuizText
quizs = new List<WCCQuizText>();
And the question is a property member of WCCQuizText:
public String question
{
get;
set;
}
After all these work, I find that the Binding cant find the property question. It seems that because of this line in the constructor of SelectableButton:
DataContext = this;
The Binding will look for the property question in Class SelectableButton, not from the ItemsSouce. Because if I bind question directly to some TextBlock.Text, it will work. But when I bind it to my UserControl, it can't be found.
So anybody know how to deal with this?
If I do like this, I can show the binding text correctly, the TextBlock is in the Pivot, too.
<TextBlock
Name="TextBlockQuestion"
Text="{Binding question}"
....
>
</TextBlock>
And my Binding:
<ShareControl:SelectableButton
SelectableButtonText="{Binding Text, ElementName=TextBlockQuestion}"
....
>
</ShareControl:SelectableButton>
You are correct. It is caused by DataContext = this. Normally your UserControl would have context set to an instance of WCCQuizText but you are overwriting it with an instance of your UserControl. Try removing that line, give UserControl some name and and change your binding, within UserControl, to something like:
<UserControl x:Name="SomeName" ... >
....
<TextBlock ... Text="{Binding ElementName=SomeName, Path=SelectableButtonText}"
also TextBlock is display control and it will always be one way binding so you can skip Mode=TwoWay

How to make this code more efficient?

As i am not very advanced in C# yet, I try to learn how to make my code more efficient.
I stored a lot of strings in some of the properties.
At the start of the application, i load all the seperatie properties into the textboxes.
I now ise this code to load them all:
private void LoadStoredStrings()
{
txtT1S1.Text = Properties.Settings.Default.strT1L1;
txtT1S2.Text = Properties.Settings.Default.strT1L2;
txtT1S3.Text = Properties.Settings.Default.strT1L3;
txtT1S4.Text = Properties.Settings.Default.strT1L4;
txtT1S5.Text = Properties.Settings.Default.strT1L5;
txtT1S6.Text = Properties.Settings.Default.strT1L6;
txtT1S7.Text = Properties.Settings.Default.strT1L7;
txtT1S8.Text = Properties.Settings.Default.strT1L8;
txtT1S9.Text = Properties.Settings.Default.strT1L9;
txtT1S10.Text = Properties.Settings.Default.strT1L10;
}
Obvious i can see the logic that each stored propertie ending with T1L1 also fits to the txt that ends with T1S1.
I just know this should be done in a more elegant and solid way than what i did now.
Could anyone push me in the right direction?
you can bind your properties directly to your textboxes
<UserControl xmlns:Properties="clr-namespace:MyProjectNamespace.Properties" >
<TextBox Text="{Binding Source={x:Static Properties:Settings.Default}, Path=strT1L1, Mode=TwoWay}" />
If you can get all of those constants into a List<string>, you could use it to bind to an ItemsControl with TextBlock inside:
Code behind or View Model
private ObservableCollection<string> _defaultProperties = new ObservableCollection<string>();
public ObservableCollection<string> DefaultProperties
{
get { return _defaultProperties; }
}
XAML
<ListBox ItemsSource="{Binding Path=DefaultProperties"}>
<ListBox.ItemTemplate>
<DataTemplate>
<!--Just saying "Binding" allows binding directly to the current data context vs. a property on the data context-->
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

MVVM binding double click to method using telerik radtreecontrol

I've been working on this problem for a stupid amount of time. It is time to ask for directions despite my inner man saying "don't do it."
I am coding in WPF C# using MVVM design pattern. We try to adhere strictly to the pattern and put nothing in the code behind unless there is no option or it is completely unreasonable to do so. Having said that, I am working with a Telerik RadTreeView. Here is a snippet of it in my XAML:
<telerik:RadTreeView IsExpandOnSingleClickEnabled="True" IsLineEnabled="True" Margin="5"
ItemsSource="{Binding ItemsView}"
SelectedItem="{Binding SelectedWidget, Mode=TwoWay}"
ItemTemplate="{StaticResource NodeTemplate}" />
Currently the tree is working properly so that if you highlight a tree item and click the OK button on the view, all is good. However, I need to also allow the user to double click on one of the tree items. This means I already have a command and method, protected override void OkAction(), in my view model with the needed logic. Telerik supplies a property called ItemDoubleClick that is supposed to supply functionality for the tree item double click. But I can't find anything to allow me to do this in the view model. In other words, how do I do the binding? We also have a behavior setup in our project for double clicking that I was told I could use, but I have no experience with behaviors. I'm still a little wet with WPF.
If it helps, here is a link to the documentation for Telerik: http://www.telerik.com/help/wpf/radtreeview-events-overview.html
I would appreciate any help or direction anyone can provide.
Try this out Stan:
<Grid.Resources>
<DataTemplate x:Key="WidgetTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="/Resources/gear1.png" Margin="1" Stretch="None" />
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" Margin="6,0,0,0" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="NodeTemplate" ItemsSource = "{Binding Children}" ItemTemplate="{StaticResource WidgetTemplate}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</Grid.Resources>
This is where you are going to want to possibly use the Attached Behavior that you already have for the DoubleClick.
Otherwise, here is the complete code that I use which creates the Attached Behavior and will create two Attached Properties which bind to a Command and optionally a Command Parameter.
AttachedBehaviors.cs
public static class MouseDoubleClick
{
public static DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand),
typeof(MouseDoubleClick),
new UIPropertyMetadata(CommandChanged));
public static DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter",
typeof(object),
typeof(MouseDoubleClick),
new UIPropertyMetadata(null));
public static void SetCommand(DependencyObject target, ICommand value)
{
target.SetValue(CommandProperty, value);
}
public static void SetCommandParameter(DependencyObject target, object value)
{
target.SetValue(CommandParameterProperty, value);
}
public static object GetCommandParameter(DependencyObject target)
{
return target.GetValue(CommandParameterProperty);
}
private static void CommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Control control = target as Control;
if (control != null)
{
if ((e.NewValue != null) && (e.OldValue == null))
{
control.MouseDoubleClick += OnMouseDoubleClick;
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
control.MouseDoubleClick -= OnMouseDoubleClick;
}
}
}
private static void OnMouseDoubleClick(object sender, RoutedEventArgs e)
{
Control control = sender as Control;
ICommand command = (ICommand)control.GetValue(CommandProperty);
object commandParameter = control.GetValue(CommandParameterProperty);
if (command.CanExecute(commandParameter))
command.Execute(commandParameter);
}
}
.xaml - Remember to add the namespace of where the Attached Behavior lies.
<telerik:RadTreeView IsExpandOnSingleClickEnabled="True"
IsLineEnabled="True"
Margin="5"
ItemsSource="{Binding ItemsView}"
SelectedItem="{Binding SelectedWidget, Mode=TwoWay}"
ItemTemplate="{StaticResource NodeTemplate}"
acb:MouseDoubleClick.Command="{Binding ShowItemCommand}" />
SampleViewModel.cs
private RelayCommand _showItemCommand;
public RelayCommand ShowItemCommand
{
get
{
return _showItemCommand ?? (_showItemCommand =
new RelayCommand(ShowItemDetails, IsItemSelected));
}
}
obviously I don't have Telerik code so I can't really help as much as i would like to but you can try something like this. (Disclaimer: I am writing from top of my head)
Define your style in Grid.Resources
<Style TargetType="{x:Type RadTreeViewItem }" x:Key="TreeViewItemStyle">
<EventSetter Event="MouseDoubleClick" Handler="{Binding DoubleClick}" />
</Style>
Add the Style to Container Style.
<telerik:RadTreeView IsExpandOnSingleClickEnabled="True" IsLineEnabled="True" Margin="5"
ItemsSource="{Binding ItemsView}"
SelectedItem="{Binding SelectedWidget, Mode=TwoWay}"
ItemTemplate="{StaticResource NodeTemplate}"
ItemContainerStyle ="{StaticResource TreeViewItemStyle}"/>
Let me know if it works.
I tried several ways to get this accomplished.In the end I found that VS2012 was giving me fits. I noticed that changes weren't being applied on a build and run.
I opened VS2010 to find I wasn't experiencing the same issues. After speaking with my boss, we found this to be a good example of a situation that adhering to MVVM may not be the wisest choice since double clicking was strictly UI related.
I simply bounced through the code behind and into the view model using the instantiation of the view model as the data context. Didn't take but a second to do that.
As for the other solutions, I am sure it is completely possible, but I cannot confirm or deny the posts I've made here because of my VS2012 issues.

Categories

Resources