How to bind a command and data in a single control? - c#

I am writing a custom control for a project. The control will hold a dropdown combobox and a datagrid. This control will be used throughout the project. On the control I am exposing the SelectedDate and the SelectedDateChangedCommand as dependency properties. When the user selects a date the main programs Model would update it's list of information for the relevant date.
When I use that control in my project the data (SelectedDate) should come from the model but the command (SelectedDateChangedCommand) should come from the ViewModel. How do I bind the data to the Model and the command to the ViewModel?
Basically, if I want to bind the content AND command of the same button, what do I do?
EDIT:
Ok, I don't think I'm explaining this very well. I'm not using a framework or template or anything like that. Maybe my problem was mentioning the UserControl. My issue is not in writing the UserControl. My issue is from the outside of the control. I don't have a code example because that is my question: how do I do this? If I were to have some sort of code it would be like this:
<Button Content="{Binding Model.SelectedDate]" Command="{Binding ViewModel.SelectedDateChanged}" />
How do I bind two properties on a control that are on two different classes?
Wow, even asking questions is difficult in WPF. :)

Ok if I understand you, you want to create a cc. Then Run bind to DateTime to your SelectedDate on your vm, and invoke a command from the control to the vm? I made a simple example for you here, I hope I understood you correctly. I simplified this example by just using a datepicker. I am using galasoft MVVM Light here. Change the content to what you want. Hope I didn't get you completely wrong :)
Generic.xaml
<Style TargetType="{x:Type local:YourCustControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:YourCustControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid >
<DatePicker x:Name="PART_DatePicker"
SelectedDate="{Binding Path=YourDateTime, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:YourCustControl}}}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
CustControl
[TemplatePart(Name = "PART_DatePicker", Type = typeof (DatePicker))]
public class YourCustControl : Control
{
public static readonly DependencyProperty SelectedDateChangedCommandProperty = DependencyProperty.Register("SelectedDateChangedCommand", typeof (ICommand), typeof (YourCustControl), new PropertyMetadata(null));
public static readonly DependencyProperty YourDateTimeProperty = DependencyProperty.Register("YourDateTime", typeof (DateTime), typeof (YourCustControl), new PropertyMetadata(null));
private DatePicker datePicker;
static YourCustControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof (YourCustControl), new FrameworkPropertyMetadata(typeof (YourCustControl)));
}
public ICommand SelectedDateChangedCommand
{
get { return (ICommand) GetValue(SelectedDateChangedCommandProperty); }
set { SetValue(SelectedDateChangedCommandProperty, value); }
}
public DateTime YourDateTime
{
get { return (DateTime) GetValue(YourDateTimeProperty); }
set { SetValue(YourDateTimeProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
datePicker = (DatePicker) Template.FindName("PART_DatePicker", this);
if (datePicker != null)
{
datePicker.SelectedDateChanged += datePicker_SelectedDateChanged;
}
}
private void datePicker_SelectedDateChanged(object sender, SelectionChangedEventArgs e)
{
// Execute the command
if (SelectedDateChangedCommand != null && SelectedDateChangedCommand.CanExecute(e) && !e.Handled)
SelectedDateChangedCommand.Execute(e);
}
}
ViewModel
// replace with whatever, like extend Galasoft's ViewModelBase
public class YourViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator] // Remove if no R#
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class YourViewModel : YourViewModelBase
{
private DateTime dateTime;
public DateTime DateTime
{
get { return dateTime; }
set
{
if (value.Equals(dateTime)) return;
dateTime = value;
OnPropertyChanged();
}
}
public ICommand SelectedDateChangedCommand { get; set; }
public YourViewModel()
{
SelectedDateChangedCommand = new RelayCommand<SelectionChangedEventArgs>(OnSelectedDateChanged);
}
private void OnSelectedDateChanged(SelectionChangedEventArgs e)
{
if (e != null)
e.Handled = true; // dirty hack
// do stuff here
}
}
Finally your xaml
<Grid>
<local:YourCustControl SelectedDateChangedCommand="{Binding SelectedDateChangedCommand}"
YourDateTime="{Binding DateTime, Mode=TwoWay}"/>
</Grid>
Hope it helps!
Cheers

Related

Bind mouseLeftButtonDown for ListBoxItem and background color on PropertyChanged (mvvm, wpf)

sorry if you think this is a duplication of another post, but I tried every possible solution out there and I can't make it work.
The question is a two parter, but it's about the same code, so I thought I can ask the question in the same thread.
I try to do order system in C#, wpf, vs2015 using mvvm without any (hard coded) couplings. There is two things I need to do. First is to fire an event which I need to capture in the ViewModel, and second, when the number of articles is over a certain level, the background of the listboxitem for that article should be green (otherwise white/grey)
For this I use a ListBox and some listBoxItems.
MainWindow.xaml (the part that matters)
<Window x:Class="Sequence_Application_2.GUI.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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:acb="clr-namespace:AttachedCommandBehavior;assembly=AttachedCommandBehavior"
mc:Ignorable="d"
....
<Window.DataContext>
<Grid Grid.Column="0" Margin="10">
<ListBox x:Name="LstViewFlows" SelectedItem="{Binding SelectedFlow.Name}" ItemsSource="{Binding Flows.Keys}" >
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" >
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedFlow.CanDeliver, UpdateSourceTrigger=PropertyChanged}" Value="true" >
<Setter Property="ListBoxItem.Background" Value="DarkGreen" />
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding SetSelectedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
Question 1)
I need to bind a command to a "MouseLeftButtonDown" event when I click on a listboxItem in a listbox. I tried the ACB-solution but can't get it to work. At last I tried the code above and I can't get it to trigger when I click on an item, but below the items, in the empty part of the listbox (not on a listboxitem), it tiggers as it should. Guess I need to rearange my xaml-file or something, but I tried for two days now I nothing seems to work.
The command is named "setSelectedCommand" and is implemented like this
internal class SetSelectedFlowCommand : ICommand
{
private FlowsViewModel _flowsViewModel;
public SetSelectedFlowCommand(FlowsViewModel flowsViewModel)
{
_flowsViewModel = flowsViewModel;
}
/// <summary>
///
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_flowsViewModel.SetSelectedFlow();
}
}
As I said, if I click on the ListBoxItem - nothing happends, but if i click in the ListBox - the command is fired (but nothing is "selected")
Question 2
As you see in the xaml above, I try to set the background color for a ListBoxitem, based on the value .CanDeliver in an object that is stored in a dictionary in the ViewModel. The variable Flow is the dictionary and SelectedFlow is supposed to be the flow that is selected.
This is an order system and CanDeliver is a variable that tells the operator/user if there is enough products to be delivered to the customer. If there are enough the listboxitem should be green, otherwise remain white/grey. Do you understand my question? Can I do it like this? Refering to an object inside a dictionary? (it's triggered by an INotifyPropertyChanged in the objects)
Hope you can help me because I have no more hair to pull from my head now ;-)
You don't need to use an event handler if all you want is to get the selectedItem. I have build an working example for you to show how to use binding only (MVVM) to achieve your requirements in your question:
C# (ViewModel):
using System;
using System.Collections.Generic;
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MyViewModel mvm = new MyViewModel()
{
Flows = new ObservableCollection<Flow>()
{
new Flow() { Name = "Flow1" },
new Flow() { Name = "Flow2" },
new Flow() { Name = "Flow3" , Amount=1},
new Flow() { Name = "Flow4" }
}
};
this.DataContext = mvm;
}
}
public class MyViewModel : ObservableObject
{
private Flow _selectedflow;
public ObservableCollection<Flow> Flows
{
get;
set;
}
public Flow SelectedFlow
{
get { return _selectedflow; }
set
{
if (value != _selectedflow)
{
_selectedflow = value;
RaisePropertyChanged("SelectedFlow");
}
}
}
}
public class Flow : ObservableObject
{
private string _name;
private int _amount;
public string Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public bool CanDeliver
{
get
{
return Amount > 0;
}
}
public int Amount
{
get { return _amount; }
set
{
if (value != _amount)
{
_amount = value;
RaisePropertyChanged("Amount");
RaisePropertyChanged("CanDeliver");
}
}
}
}
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
protected void RaisePropertyChanged(String propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="350">
<Grid>
<StackPanel Orientation="Vertical">
<ListBox SelectedItem="{Binding SelectedFlow}" ItemsSource="{Binding Flows}" DisplayMemberPath="Name">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" >
<Style.Triggers>
<DataTrigger Binding="{Binding CanDeliver, UpdateSourceTrigger=PropertyChanged}" Value="true" >
<Setter Property="Background" Value="DarkGreen" />
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<TextBlock Text="{Binding SelectedFlow.Name}"/>
</StackPanel>
</Grid>
</Window>
Result: You can see the Flow item with CanDeliver = True (Amount>0) has a Green background. And the TextBlock is showing the SelectedFlow.

wpf fire PropertyChanged event in binding source that binding gets updated

I'm trying to make some CustomControl Textboxes with validation features.
(for example a number only textbox or a zip-code textbox etc.)
It should realized in a .dll library file.
My project contains a CustomControl for the textbox, a class wich handles the validations,
and a ErrMsgGui CustomControl that should show a error message in a TextBlock
(exmp.: Only numbers allowed...)
My problem is that I don't get the TextBlock Text updated when a method in the validation class is called
Is there a way to trigger the PropertyChangeEvent which updates the Textblock text within the validaiton class?
(Im quite new to wpf)
Generic.xaml:
<Style TargetType="{x:Type local:NumTb}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NumTb}">
<TextBox Background="{TemplateBinding Background}" Text="{Binding Source={StaticResource NumTbVm}, Path=NumTbText, UpdateSourceTrigger=PropertyChanged}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:ErrMsgGui}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ErrMsgGui}">
<TextBlock Text="{ Binding Source={StaticResource val}, Path=ErrMsgGuiText, UpdateSourceTrigger=PropertyChanged}" Background="{TemplateBinding Background}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Validations.cs:
private const string ONLY_NUMBERS_REGEX = #"^[0-9]+$"; //Nur Zahlen
private string _ErrMsgGuiText;
public string ErrMsgGuiText
{
get { return _ErrMsgGuiText; }
set
{
_ErrMsgGuiText = value;
Debug.Print("QueryText: " + value);
OnPropertyChanged("ErrMsgGuiText");
}
}
public object[] onlyNumbers(string s2c, bool output)
{
object[] objRes = new object[2];
bool result = true;
string errMsg = "";
Regex regex = new Regex(ONLY_NUMBERS_REGEX);
if (s2c != null && s2c != "" && !regex.IsMatch(s2c))
{
result = false;
errMsg = "Nur Zahlen sind zulässig";
}
objRes[0] = result;
objRes[1] = errMsg;
if (output == true)
{
ErrMsgGuiText = errMsg;
}
return objRes;
}
public void onlyNumbers(string s2c)
{
onlyNumbers(s2c, true);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
NumTbViewModel.cs:
Validations val = null;
public NumTbViewModel()
{
val = new Validations();
}
private string _NumTbText;
public string NumTbText
{
get { return _NumTbText; }
set
{
_NumTbText = value;
this.OnPropertyChanged("NumTbText");
val.onlyNumbers(_NumTbText);
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
It looks like the TextBlock source is looking at a static resource for the Validations class and the Validations called in your NumTbViewModel is NOT the same as the static resource. A solution could be to add a property to NumTbViewModel.cs and point your binding to that property so the Validations class instances will be the same. In NumTbViewModel.cs add:
Validations _val;
public Validations Val
{
get { return _val; }
set
{
_val = value;
this.OnPropertyChanged("Val");
}
}
Change your source and path in xaml binding on the TextBlock:
<TextBlock Text="{ Binding Source={StaticResource NumTbVm}, Path=Val.ErrMsgGuiText, UpdateSourceTrigger=PropertyChanged}" Background="{TemplateBinding Background}"/>
Another way:
You could also set the Val property of your NumTbViewModel when you define your static resource like so:
<local:Validations x:Key="val" />
<local:NumTbViewModel x:Key="NumTbVm" Val="{StaticResource val}" />
Doing this you can keep the bindings like you originally had.

WPF DataBinding Issues - Possible Noob Problems

I am trying to bind a ViewModel property of type Visibility to the visibility property on a Dock Panel:
Updated ViewModel Code:
public class SelectWaferButtonViewModel : INotifyPropertyChanged
{
private bool isClicked;
public SelectWaferButtonViewModel()
{
isClicked = false;
}
public bool IsControlVisible
{
get
{
return isClicked;
}
set
{
isClicked = value;
OnPropertyChanged("IsControlVisible");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnButtonClick()
{
if (isClicked)
{
IsControlVisible = false;
}
else
{
IsControlVisible = true;
}
}
protected virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
and here is my updated XAML code:
<DockPanel
Name="tvwDockPanel"
Width="200"
Visibility="{Binding IsControlVisible, FallbackValue=Collapsed, Converter={StaticResource BoolToVisConverter}}"
DockPanel.Dock="Left">
<DockPanel
DockPanel.Dock="Top"
Height="22">
</DockPanel>
and I set the data context in the code behind with this line:
tvwDockPanel.DataContext = btnSelectWaferViewModel;
where btnSelectWaferViewModel is the ViewModel object for this situation.
and for fun, here is my code behind:
public partial class WaferTrackerWindow : Window
{
List<ISubscribeEvents> subscriptionList;
SelectWaferButtonViewModel btnSelectWaferViewModel;
public WaferTrackerWindow()
{
InitializeComponent();
this.InstantiateObjects();
this.SubscribeEvents();
this.SetDataContexts();
}
#region Methods
private void SetDataContexts()
{
tvwDockPanel.DataContext = btnSelectWaferViewModel.IsControlVisible;
}
private void SubscribeEvents()
{
foreach (ISubscribeEvents subscriber in subscriptionList)
{
subscriber.SubscribeEvents();
}
}
private void InstantiateObjects()
{
btnSelectWaferViewModel = new SelectWaferButtonViewModel();
subscriptionList = new List<ISubscribeEvents>();
subscriptionList.Add(
new Classes.WaferTrackerWindow.SelectWaferButtonView(btnSelectWafer, btnSelectWaferViewModel));
}
#endregion
}
All I want to do click the button btnSelectWafer and have the tvwDockPanel's visibility property to get to set to Visible via binding. Then when you click again on btnSelectWafer, tvwDockPanel's visibility property gets set back to Collapsed again. tvwDockPanel's visibility will only ever be either Collapsed or Visible.
Any help would be awesome, I am rather new to this whole data binding concept.
You have several issues here:
First of all, the intent of MVVM (if you're trying to do this with MVVM) is to separate logic from presentation. This means that in no way your ViewModel can have a reference to System.Windows.Controls.Button, nor to System.Windows.Visibility, nor to any other classes inside the System.Windows Namespace.
It is not clear to me what your SelectWaferButtonViewModel class is doing with the Button, but you need to remove the Button from there.
Also, If you need to manipulate the Visibility of a control from the ViewModel layer, you'd better use a Boolean property and the BooleanToVisibilityConverter in XAML:
ViewModel:
public bool IsControlVisible {get;set;} //Don't forget INotifyPropertyChanged!!
XAML:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
</Window.Resources>
<DockPanel Visibility="{Binding IsControlVisible, Converter={StaticResource BoolToVisConverter}}"/>
The problem is that you're binding your DockPanel to the boolean property of your view model, and then setting the Visiblity property of your UI element to the IsControlVisible property of the datacontext (which doesn't exist).
Change to:
private void SetDataContexts()
{
tvwDockPanel.DataContext = btnSelectWaferViewModel;
}

Making AvalonEdit MVVM compatible

I'm trying to make Avalon MVVM compatible in my WPF application. From googling, I found out that AvalonEdit is not MVVM friendly and I need to export the state of AvalonEdit by making a class derived from TextEditor then adding the necessary dependency properties. I'm afraid that I'm quite lost in Herr Grunwald's answer here:
If you really need to export the state of the editor using MVVM, then I suggest you create a class deriving from TextEditor which adds the necessary dependency properties and synchronizes them with the actual properties in AvalonEdit.
Does anyone have an example or have good suggestions on how to achieve this?
Herr Grunwald is talking about wrapping the TextEditor properties with dependency properties, so that you can bind to them. The basic idea is like this (using the CaretOffset property for example):
Modified TextEditor class
public class MvvmTextEditor : TextEditor, INotifyPropertyChanged
{
public static DependencyProperty CaretOffsetProperty =
DependencyProperty.Register("CaretOffset", typeof(int), typeof(MvvmTextEditor),
// binding changed callback: set value of underlying property
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
target.CaretOffset = (int)args.NewValue;
})
);
public new string Text
{
get { return base.Text; }
set { base.Text = value; }
}
public new int CaretOffset
{
get { return base.CaretOffset; }
set { base.CaretOffset = value; }
}
public int Length { get { return base.Text.Length; } }
protected override void OnTextChanged(EventArgs e)
{
RaisePropertyChanged("Length");
base.OnTextChanged(e);
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Now that the CaretOffset has been wrapped in a DependencyProperty, you can bind it to a property, say Offset in your View Model. For illustration, bind a Slider control's value to the same View Model property Offset, and see that when you move the Slider, the Avalon editor's cursor position gets updated:
Test XAML
<Window x:Class="AvalonDemo.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
xmlns:avalonExt="clr-namespace:WpfTest.AvalonExt"
DataContext="{Binding RelativeSource={RelativeSource Self},Path=ViewModel}">
<StackPanel>
<avalonExt:MvvmTextEditor Text="Hello World" CaretOffset="{Binding Offset}" x:Name="editor" />
<Slider Minimum="0" Maximum="{Binding ElementName=editor,Path=Length,Mode=OneWay}"
Value="{Binding Offset}" />
<TextBlock Text="{Binding Path=Offset,StringFormat='Caret Position is {0}'}" />
<TextBlock Text="{Binding Path=Length,ElementName=editor,StringFormat='Length is {0}'}" />
</StackPanel>
</Window>
Test Code-behind
namespace AvalonDemo
{
public partial class TestWindow : Window
{
public AvalonTestModel ViewModel { get; set; }
public TestWindow()
{
ViewModel = new AvalonTestModel();
InitializeComponent();
}
}
}
Test View Model
public class AvalonTestModel : INotifyPropertyChanged
{
private int _offset;
public int Offset
{
get { return _offset; }
set
{
_offset = value;
RaisePropertyChanged("Offset");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
You can use the Document property from the editor and bind it to a property of your ViewModel.
Here is the code for the view :
<Window x:Class="AvalonEditIntegration.UI.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:AvalonEdit="clr-namespace:ICSharpCode.AvalonEdit;assembly=ICSharpCode.AvalonEdit"
Title="Window1"
WindowStartupLocation="CenterScreen"
Width="500"
Height="500">
<DockPanel>
<Button Content="Show code"
Command="{Binding ShowCode}"
Height="50"
DockPanel.Dock="Bottom" />
<AvalonEdit:TextEditor ShowLineNumbers="True"
Document="{Binding Path=Document}"
FontFamily="Consolas"
FontSize="10pt" />
</DockPanel>
</Window>
And the code for the ViewModel :
namespace AvalonEditIntegration.UI
{
using System.Windows;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Document;
public class ViewModel
{
public ViewModel()
{
ShowCode = new DelegatingCommand(Show);
Document = new TextDocument();
}
public ICommand ShowCode { get; private set; }
public TextDocument Document { get; set; }
private void Show()
{
MessageBox.Show(Document.Text);
}
}
}
source : blog nawrem.reverse
Not sure if this fits your needs, but I found a way to access all the "important" components of the TextEditor on a ViewModel while having it displayed on a View, still exploring the possibilities though.
What I did was instead of instantiating the TextEditor on the View and then binding the many properties that I will need, I created a Content Control and bound its content to a TextEditor instance that I create in the ViewModel.
View:
<ContentControl Content="{Binding AvalonEditor}" />
ViewModel:
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
// ...
private TextEditor m_AvalonEditor = new TextEditor();
public TextEditor AvalonEditor => m_AvalonEditor;
Test code in the ViewModel (works!)
// tests with the main component
m_AvalonEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("XML");
m_AvalonEditor.ShowLineNumbers = true;
m_AvalonEditor.Load(#"C:\testfile.xml");
// test with Options
m_AvalonEditor.Options.HighlightCurrentLine = true;
// test with Text Area
m_AvalonEditor.TextArea.Opacity = 0.5;
// test with Document
m_AvalonEditor.Document.Text += "bla";
At the moment I am still deciding exactly what I need my application to configure/do with the textEditor but from these tests it seems I can change any property from it while keeping a MVVM approach.

Silverlight treeview: save expanded/collapsed state

I'm using a hierarchical tree view in Silverlight 4. This tree can be cleared and rebuilded quite often, depending on the user's actions. When this happends, the tree is collapsed by default, which can be annoying from a user perspective.
So, I want to somehow save which nodes are expanded so I can restore the visual state of my tree after it is clear and reloaded.
My treeview is implemented like this:
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:controls2="clr-namespace:System.Windows;assembly=System.Windows.Controls"
<controls:TreeView x:Name="Tree"
ItemsSource="{Binding Source={StaticResource ViewModel}, Path=TreeStructure, Mode=OneWay}"
ItemTemplate="{StaticResource hierarchicalTemplate}" />
<controls2:HierarchicalDataTemplate x:Key="hierarchicalTemplate" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Value.DisplayName}">
</controls2:HierarchicalDataTemplate>
ItemsSource of my treeview is bound on an ObservableCollection TreeStructure;
Node is a wrapper class that looks like that:
public class Node
{
public object Value { get; private set; }
public ObservableCollection<Node> Children { get; private set; }
public Node(object value)
{
Value = value;
Children = new ObservableCollection<Node>();
}
}
Pretty standard stuff. I saw some solutions for WPF, but I can find anything for the Silverlight tree view...
Any suggestions?
Thanks!
Given the way you are implementing your data as a tree, why not bind the 'TreeViewItem.IsExpanded` dependency property to a bool property on your own Node?
It will need to be an INotifyPropertyChanged property at a minimum so Node will need to implement INotifyPropertyChanged.
In Silverlight 5 you can just set a style like this to bind to the IsExpanded property:
<Style TargetType="sdk:TreeViewItem" x:Key="itemStyle">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
And use with
ItemContainerStyle="{Binding Source={StaticResource itemStyle}}"
In Silverlight 4 there are a number of workarounds.
Here's what I did to bind on the TreeViewItem.IsExpanded property. First, I added an IsExpanded property in my Node class.
public class Node : INotifyPropertyChanged
{
public object Value { get; private set; }
public ObservableCollection<Node> Children { get; private set; }
private bool isExpanded;
public bool IsExpanded
{
get
{
return this.isExpanded;
}
set
{
if (this.isExpanded != value)
{
this.isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
}
public Node(object value)
{
Value = value;
Children = new ObservableCollection<Node>();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
After that, I subclassed the TreeView and TreeViewItem controls (I lose the custom theme on my treeview, but whatever...)
public class BindableTreeView : TreeView
{
protected override DependencyObject GetContainerForItemOverride()
{
var itm = new BindableTreeViewItem();
itm.SetBinding(TreeViewItem.IsExpandedProperty, new Binding("IsExpanded") { Mode = BindingMode.TwoWay });
return itm;
}
}
public class BindableTreeViewItem : TreeViewItem
{
protected override DependencyObject GetContainerForItemOverride()
{
var itm = new BindableTreeViewItem();
itm.SetBinding(TreeViewItem.IsExpandedProperty, new Binding("IsExpanded") { Mode = BindingMode.TwoWay });
return itm;
}
}
In my XAML, I just have to use BindableTreeView instead of TreeView, and it works.
The trick is to use SetterValueBindingHelper from here. Then your XAML will look like the following. Make sure you carefully copy what I have below.
<sdk:TreeView.ItemContainerStyle>
<Style TargetType="sdk:TreeViewItem">
<Setter Property="local:SetterValueBindingHelper.PropertyBinding">
<Setter.Value>
<local:SetterValueBindingHelper>
<local:SetterValueBindingHelper Property="IsSelected" Binding="{Binding Mode=TwoWay, Path=IsSelected}"/>
<local:SetterValueBindingHelper Property="IsExpanded" Binding="{Binding Mode=TwoWay, Path=IsExpanded}"/>
</local:SetterValueBindingHelper>
</Setter.Value>
</Setter>
</Style>
</sdk:TreeView.ItemContainerStyle>
The syntax isn't exactly like what you would use in WPF, but it works and it works well!

Categories

Resources