Make active tab visible in scrollviewer - c#

I have a lot of tabs in my tabcontrol, so I used the solution from want to make scrollable tabs for a tabcontrol.
The problem is that in my window I have buttons Previous-Next, that change active tab. So I want to scrollviewer to move automatically to active tab. It is possible to make by using BringIntoView() method of a FrameworkElement. But how can I implement it in my case?

here you go, I solved the issue by creating an attached behavior (Attached Properties)
if your template resembles as in the link you've posted
add the following style with the binding the attached property ScrollHelper.SelectScroll to IsSelected of the tab item
<TabControl>
<TabControl.Resources>
<Style TargetType="TabItem" xmlns:l="clr-namespace:CSharpWPF">
<Setter Property="l:ScrollHelper.SelectScroll"
Value="{Binding IsSelected,RelativeSource={RelativeSource Self}}" />
</Style>
</TabControl.Resources>
...
</TabControl>
behavior class
namespace CSharpWPF
{
class ScrollHelper : DependencyObject
{
public static bool GetSelectScroll(DependencyObject obj)
{
return (bool)obj.GetValue(SelectScrollProperty);
}
public static void SetSelectScroll(DependencyObject obj, bool value)
{
obj.SetValue(SelectScrollProperty, value);
}
// Using a DependencyProperty as the backing store for SelectScroll. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectScrollProperty =
DependencyProperty.RegisterAttached("SelectScroll", typeof(bool), typeof(ScrollHelper), new PropertyMetadata(false, OnSelectScroll));
private static void OnSelectScroll(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TabItem tab = d as TabItem;
if ((bool)e.NewValue)
{
tab.BringIntoView();
}
}
}
}
upon change of the property it will invoke BringIntoView() method which will pull the tab into view hence scrollviewer will scroll to the tab
you may choose to rename the property or the class as per your liking, I simply chose a random name what came to my mind.

Related

Change propery of control, that has a TwoWay binding

I have a custom control with bool property. The property is binding include template control with Popup.
XAML control:
<controls:AutoCompleteTextBox x:Name="PART_Editor"
IsEnabled="False"
IsPopupOpen="{Binding IsAutocompletePopupOpen}" />
Property in the control:
public bool IsPopupOpen
{
get => (bool)GetValue(IsPopupOpenProperty);
set => SetValue(IsPopupOpenProperty, value);
}
public static readonly DependencyProperty IsPopupOpenProperty =
DependencyProperty.Register("IsPopupOpen", typeof(bool), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
Binding to element include in template control:
<Popup x:Name="PART_AutoCompletePopup"
IsOpen="{Binding IsPopupOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
I need change property IsPopupOpen on click. I decided to do this in behavior, but I need my control to be disabled. Therefore I was add behavior to container of control
<Grid>
<controls:AutoCompleteTextBox x:Name="PART_Editor"
IsEnabled="False"
IsPopupOpen="{Binding IsAutocompletePopupOpen}"/>
<i:Interaction.Behaviors>
<behaviors:PopupContainerBehavior IsPopupOpen="{Binding IsAutocompletePopupOpen, Mode=TwoWay}" />
</i:Interaction.Behaviors>
</Grid>
Behavior code:
public class PopupContainerBehavior : Behavior<UIElement>
{
public bool IsPopupOpen
{
get { return (bool)GetValue(IsPopupOpenProperty); }
set { SetValue(IsPopupOpenProperty, value); }
}
public static readonly DependencyProperty IsPopupOpenProperty =
DependencyProperty.Register("IsPopupOpen", typeof(bool), typeof(PopupContainerBehavior), new PropertyMetadata(false));
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseLeftButtonDown += OnMouseLeftButtonUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewMouseLeftButtonDown -= OnMouseLeftButtonUp;
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
IsPopupOpen = true;
}
}
The problem is that the property first changes to true and then immediately changes to false.Through SNOOP you can see this by the flashing value of the property.I think the problem lies in TwoWay Binding, but I don't know how to fix it
The reason why this is happening is due to mouse capture.
The behavior's PreviewMouseLeftButtonDown event handler tells the Popup to open
The Popup captures the mouse
The rest of the click events fire for the container
The container takes away mouse capture from the Popup
The Popup immediately closes
This can be a tricky problem to solve.
You might want to consider making your control able to open the Popup when it is disabled, vs. trying to do it from the outside. Even though a control is disabled, you can make parts of it clickable by setting IsHitTestVisible on the parts you need to be interactive, etc.

Proper way to create Custom Bindable WPF Control

I want to ask about the right way if I want to create Bindable user control consisting of two controls. I am not sure about what I am doing - whether I do it correctly , because I run into some problems.
Here is what I am trying to do:
Lets call this control ucFlagControl . Create new , custom user control ...
Its purpose is to show Color interpretation of logic ( True/ False ) value in variable , type of Bool.
What I used to do before was that I use Rectangle, and Bind FillProperty to boolean value using Converter
What I did to make it works was , that I made a usercontrol , and put rectangle and label inside
than I added this code:
public partial class ucStatusFlag : UserControl
{
public ucStatusFlag()
{
InitializeComponent();
}
public string LabelContent
{
get { return (string)GetValue(LabelContentProperty); }
set
{
SetValue(LabelContentProperty, value);
OnPropertyChanged("LabelContent");
}
}
///in case that I use integer or array
public int BitIndex
{
get { return (int)GetValue(BitIndexProperty); }
set
{
SetValue(BitIndexProperty, value);
OnPropertyChanged("BitIndex");
}
}
public string BindingSource
{
get { return (string)GetValue(BindingSourceProperty); }
set
{
SetValue(BindingSourceProperty, value);
OnPropertyChanged("BindingSource");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
/// <summary>
/// Identified the Label dependency property
/// </summary>
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register("LabelContent", typeof(string), typeof(ucStatusFlag), new PropertyMetadata("LabelContent"));
public static readonly DependencyProperty BitIndexProperty =
DependencyProperty.Register("BitIndex", typeof(int), typeof(ucStatusFlag), new PropertyMetadata(0));
public static readonly DependencyProperty BindingSourceProperty =
DependencyProperty.Register("(BindingSource", typeof(string), typeof(ucStatusFlag), new PropertyMetadata(""));
private void StatusFlag_Loaded(object sender, RoutedEventArgs e)
{
if (BindingSource.Length > 0)
{
Binding bind = new Binding();
string s = LabelContent;
int i = BitIndex;
bind.Converter = new StatusToColor();
bind.Path = new PropertyPath(BindingSource);
bind.ConverterParameter = BitIndex.ToString();
bind.Mode = BindingMode.OneWay;
bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
recStatusBit.SetBinding(Rectangle.FillProperty, bind);
}
}
private class StatusToColor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
byte bDataWordIdx;
byte bDataBitIdx;
Byte.TryParse((string)parameter, out bDataBitIdx);
if (Object.ReferenceEquals(typeof(UInt16[]), value.GetType()))
{
UInt16[] uiaData = (UInt16[])value;
bDataWordIdx = (byte)uiaData[0];
if ((uiaData[bDataBitIdx / 16] >> (bDataBitIdx % 16) & 0x1) == 1)
{
return Brushes.Green;
}
else
{
return Brushes.Red;
}
}
else if (Object.ReferenceEquals(typeof(UInt16), value.GetType()))
{
UInt16 uiaData = (UInt16)value;
if (((uiaData >> bDataBitIdx) & 0x1) == 1)
{
return Brushes.Green;
}
else
{
return Brushes.Red;
}
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return 0;
}
}
}
}
Than I realized that I can easily bind content and I do not have to create public static readonly DependencyProperty LabelContentProperty
but just property
public new string Content
{
get { return (string)label.Content; }
set
{
SetValue(label.Content, value);
OnPropertyChanged("Content");
}
}
this overrides the original content so I am able to Bind and/or assign the text of the label in upper level - in e.g. MainWindow.xaml where this user control is put
First question is if this is in this case OK or if there is some background I am not aware of and I should even such small controls do in different way - I would like to make dll. from it an load it to toolbox - I tested it works. And than use it in for example stack panel .
Second question is that I have problem with a rectangle "Fill" property . I am not able to bind that property like I bind content .
I know that the rectangle is derived from Shape class so I am not sure if it has something to do with this.
If I am able to do the inner binding or connection same as in
Content
I can remove the converters than and just bind it in e.g. MainWindow.xaml file (using the converter and converter parameter )
But FillProperty does not work for me so I am not sure about my point of view .
Thank you for suggestions
EDIT:
well I am sorry but I did not catch all you want to say in a comment below. Could you please explain closer ?
I know that the code above is not the right way to do it ... ?
Or can you post any article about it ?
my actual code is like this:
In a user control ... I removed all the code from code behind ...
' <Label x:Name="lStatusBit" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" Margin="2,1,17,2" />
<Rectangle x:Name="recStatusBit" Margin="0,3,1,7" />'
Content property works, I cant see Rectangle , and rectangle fill property ...
Other problem is if I fill in Content property in XAML where my uc is placed , Rectangle disappears .
I know I'm a year late to the party, but I'll answer incase anyone else comes across this.
My Suggestions
You should use a TextBlock control instead of Label controls if you want to display pure text. Labels have a content element which is re-rendered/computed many more times than a TextBlock's simple Text property.
You should avoid using magic strings, e.g. "LabelContent". You should use the C# nameof() expression when referencing property names. For example:
I use lambda expressions to clean up the code a bit, but this is just preference.
public string LabelContent
{
get => (string)GetValue(LabelContentProperty);
set => SetValue(LabelContentProperty, value);
}
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register(
nameof(LabelContent),
typeof(string),
typeof(ucStatusFlag),
new PropertyMetadata("Default Value"));
This will prevent runtime errors due to mistyped text, will allow you to jump to the property's reference, will make refactoring easier, and will make debugging easier by giving you a compile error that's easy to find (if the property doesn't exist).
I don't think you need the rectangle. If you're just trying to change the background color of the text area you can use a DataTrigger or make a converter.
DataTrigger Example
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<!-- The default value -->
<Setter Property="Background" Value="Transparent" />
<!-- Your trigger -->
<Style.Triggers>
<DataTrigger Binding="{Binding SomeBooleanValue}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
A DataTrigger is a quick and easy way to style a control by binding to a property on your ViewModel (assuming you're using the MVVM structure), but there are some cons - like reusing the same style on a different View whose ViewModel's properties are different. You'd have to rewrite the entire styling again.
Lets turn it into a reusable control where we can (1) specify a highlight background color, and (2) use a boolean to determine whether the control is highlighted.
Template Controls vs UserControls
I make my templated controls in a separate C# class file and put the control's styling in another separate resource dictionary file instead of using a UserControl.
These templated controls can consist of several other controls to make a single reusable control.
It's my understanding that UserControls are meant to use multiple templated controls (e.g. a TextBox) and link their interactions together to perform a specific way.
I don't think these controls are meant to be reusable in separate unrelated projects - they display data depending on your ViewModel which can be situational.
If you want to extend your custom control in the future via inheritance, then using a UserControl will make things difficult.
Here's what a few of my controls look like in the solution explorer:
Solution Files Snippet
The ExpansionPanel control in the snippet is an Expander with additional functionalities/properties.
The NavButton is a Button with additional functionalities/properties also.
I have a NavigationView UserControl that uses both of those controls to create something much larger than a templated control.
It sounds like you want to create a reusable templated control.
Creating a Custom Control
Here are the basic steps:
Create a "Themes" folder at the root of your project. It must be at the root of your project and spelling does matters.
Create a Generic.xaml Resource Dictionary file in the "Themes" folder. It must be directly under the "Themes" folder and spelling does matters.
This is where you store the default themes for your custom controls.
The template style for your control will automatically be added to the Generic.xaml file when you add a Custom Control template to your project.
<Style TargetType="{x:Type local:Example}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Example}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Personally, I like to have separate .xaml file for each control, and then I merge it into the Generic.xaml resource dictionary. This is just for organization purposes.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<!-- Control template styles -->
<ResourceDictionary Source="pack://application:,,,/Themes/ExpansionPanel.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/NavButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextDocument.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextDocumentToolBar.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextEditor.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/HighlightTextBlock.xaml" />
<!-- etc... -->
</ResourceDictionary.MergedDictionaries>
<!-- Other styles or whatever -->
</ResourceDictionary>
It's important to note that order does matter if you have controls that depend on other controls.
Merge the Generic.xaml file into your App.xaml file.
<Application>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Other resource dictionaries... -->
<ResourceDictionary Source="pack://application:,,,/Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Other resource dictionaries... -->
</ResourceDictionary>
</Application.Resources>
</Application>
Why not just merge the control templates in the App.xaml file directly? WPF looks directly for the Generic.xaml file for custom type themes. App.xaml is also application specific and wouldn't be able to be usable in other applications if you used the library as a control library.
Create a .cs file using the built in Custom Control template OR a standard C# class file.
Your control's .cs file would resemble something similar to...
public class HighlightTextBlock : Control
{
#region Private Properties
// The default brush color to resort back to
public Brush DefaultBackground;
#endregion
static HighlightTextBlock()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(HighlightTextBlock), new FrameworkPropertyMetadata(typeof(HighlightTextBlock)));
}
// Get the default background color and set it.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
DefaultBackground = Background;
}
#region Dependency Properties
/// <summary>
/// The text to display.
/// </summary>
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
nameof(Text), typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(string.Empty));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
/// <summary>
/// Whether or not the background should be highlighted.
/// </summary>
// This uses a callback to update the background color whenever the value changes
public static readonly DependencyProperty HighlightProperty = DependencyProperty.Register(
nameof(Highlight), typeof(bool),
typeof(HighlightTextBlock), new PropertyMetadata(false, HighlightPropertyChangedCallback));
public bool Highlight
{
get => (bool)GetValue(HighlightProperty);
set => SetValue(HighlightProperty, value);
}
/// <summary>
/// The highlight background color when <see cref="Highlight"/> is true.
/// </summary>
public static readonly DependencyProperty HighlightColorProperty = DependencyProperty.Register(
nameof(HighlightColor), typeof(Brush),
typeof(HighlightTextBlock), new PropertyMetadata(null));
public Brush HighlightColor
{
get => (Brush)GetValue(HighlightColorProperty);
set => SetValue(HighlightColorProperty, value);
}
#endregion
#region Callbacks
// This is the callback that will update the background
private static void HighlightPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var target = (HighlightTextBlock)dependencyObject;
if (target.Highlight)
target.Background = target.HighlightColor;
else
target.Background = target.DefaultBackground;
}
#endregion
}
Create a ResourceDictionary.xaml file to store your control's template and style OR add it directly in Generic.xaml.
Your .xaml file would look something like...
<Style x:Key="HighlightTextBlock" TargetType="{x:Type ctrl:HighlightTextBlock}">
<!-- Default setters... -->
<!-- Define your control's design template -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ctrl:HighlightTextBlock}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<!--
I only bound the Text and Background property in this example
Make sure to bind other properties too.. like Visibility, IsEnabled, etc..
-->
<TextBlock Text="{TemplateBinding Text}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--
Set the default style for the control
The above style has a key, so controls won't use that style
unless the style is explicitly set.
e.g.
<ctrl:HighlightTextBlock Style={StaticResource HighlightTextBlock} />
The reason I used a key above is to allow extending/reusing that default style.
If a key wasn't present then you wouldn't be able to reference it in
another style.
-->
<Style TargetType="{x:Type ctrl:HighlightTextBlock}" BasedOn="{StaticResource HighlightTextBlock}" />
Add a reference to the control's resource dictionary in Generic.xaml, like in step 2's code snippet.
Usage:
I'm binding the IsChecked property to a IsHighlighted property on my ViewModel.
You can bind it to whatever.
<StackPanel>
<ToggleButton IsChecked="{Binding IsHighlighted}" Content="{Binding IsHighlighted}"
Width="100" Height="35" Margin="5"/>
<ctrl:HighlightTextBlock Background="Transparent" HighlightColor="Red"
Text="HELLO WORLD!!!" Highlight="{Binding IsHighlighted}"
Width="100" Height="35" HorizontalAlignment="Center" />
</StackPanel>
On False Snippet
On True Snippet
Your controls may look a bit different - I'm using a custom dark theme.
Finally I found this working :
XAML
<UserControl x:Class="ucStatusFlag"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="17" d:DesignWidth="100"
x:Name="StatusFlag">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle x:Name="recStatusBit" Grid.Column="0" Stroke="Black" Width="11" Fill="{Binding ElementName=StatusFlag, Path=RectangleColor}" Margin="0,2,0.2,3.8" />
<Label Height="17" x:Name="lStatusBit" Foreground="Black" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" Margin="5,0,0,1" Content="{Binding ElementName=StatusFlag, Path=LabelContent}" />
</Grid>
C#
public partial class ucStatusFlag : UserControl
{
public ucStatusFlag()
{
InitializeComponent();
}
public string LabelContent
{
get { return (string)GetValue(LabelContentProperty); }
set
{
SetValue(LabelContentProperty, value);
}
}
public SolidColorBrush RectangleColor
{
get { return (SolidColorBrush)GetValue(RectangleColorProperty); }
set
{
SetValue(RectangleColorProperty, value);
}
}
public static readonly DependencyProperty RectangleColorProperty =
DependencyProperty.Register("RectangleColor", typeof(SolidColorBrush), typeof(ucStatusFlag), new PropertyMetadata(Brushes.Gold));
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register("LabelContent", typeof(string), typeof(ucStatusFlag), new PropertyMetadata("LabelContent"));
}
Binding in Another Project :
<ucStatusFlag HorizontalAlignment="Left" Height="18" Margin="154,224,0,0" VerticalAlignment="Top" Width="100" LabelContent="ABC" RectangleColor="{Binding RectangleColorPropertyInProject}"/>
Where RectangleColorPropertyInProject is Property In certain project view model

How to continuously scroll to bottom of scrollviewer MVVM after interruption

I have previously used the Attached Property which was the top response to this question. I used it on a text block that was an output window for a background process. However I have notice that, when I scroll up inside the scroll viewer, the scroll viewer stops scrolling to the bottom.
I cannot figure out how to ensure that the scroll viewer continues scrolling to the bottom. Please could you suggest reasons why this might be happening or how I might go about rectifying this issue without code behind.
You can simply change the attached property to listen to changes in the property the TextBlock's Text is bound to, so whenever that changes your ScrollViewer will scroll to the bottom.
Usage:
<ScrollViewer HorizontalScrollBarVisibility="Auto" myApp:ScrollViewerAttachedProperties.ScrollToBottomOnChange="{Binding Logs}">
<TextBlock Text="{Binding Path=Logs}" />
</ScrollViewer>
The attached property:
public static class ScrollViewerAttachedProperties
{
public static readonly DependencyProperty ScrollToBottomOnChangeProperty = DependencyProperty.RegisterAttached(
"ScrollToBottomOnChange", typeof(object), typeof(ScrollViewerAttachedProperties), new PropertyMetadata(default(ScrollViewer), OnScrollToBottomOnChangeChanged));
private static void OnScrollToBottomOnChangeChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var scrollViewer = dependencyObject as ScrollViewer;
scrollViewer?.ScrollToBottom();
}
public static void SetScrollToBottomOnChange(DependencyObject element, object value)
{
element.SetValue(ScrollToBottomOnChangeProperty, value);
}
public static object GetScrollToBottomOnChange(DependencyObject element)
{
return element.GetValue(ScrollToBottomOnChangeProperty);
}
}

UserControl depends on TreeView's (WPF) SelectedItem

I have two user controls, one contains a TreeView, one contains a ListView.
The TreeView has an itemsource and hierarchical data templates that fill the nodes and leafes (node=TvShow, leaf=Season).
The ListView should show the children of the selected TreeView item (thus, the selected season): the episodes of that season.
This worked fine when I had both the TreeView and the Listview defined in the same window, I could use something like this:
<ListView
x:Name="_listViewEpisodes"
Grid.Column="2"
ItemsSource="{Binding ElementName=_tvShowsTreeView, Path=SelectedItem.Episodes}">
How can I achieve this, when both controls are defined in separate user controls? (because in the context of one user control, I miss the context of the other user control)
This seems something pretty basic and I am getting frustrated that I can't figure it out by myself. I refuse to solve this with code-behind, I have a very clean MVVM project so far and I would like to keep it that way.
Hope that somebody can give me some advise!
First of all you have to created the SelectedValue proeprty in your ViewModel and bind the TreeView.SelectedItem property to it. Since the SelectedItem property is read-only I suggest you to create a helper to create OneWayToSource-like binding. The code should be like the following:
public class BindingWrapper {
public static object GetSource(DependencyObject obj) { return (object)obj.GetValue(SourceProperty); }
public static void SetSource(DependencyObject obj, object value) { obj.SetValue(SourceProperty, value); }
public static object GetTarget(DependencyObject obj) { return (object)obj.GetValue(TargetProperty); }
public static void SetTarget(DependencyObject obj, object value) { obj.SetValue(TargetProperty, value); }
public static readonly DependencyProperty TargetProperty = DependencyProperty.RegisterAttached("Target", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null));
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached("Source", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null, OnSourceChanged));
static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
SetTarget(d, e.NewValue);
}
}
The idea is simple: you have two attached properties, the Source and the Target. When the first one changes the PropertyChangedCallback is called and you simply setting the NewValue as the Target property value. In my opinion this scenario is helpful in a lot of cases when you need to bind the read-only property in XAML (especially in control templates).
I've created a simple model to demonstrate how to use this helper:
public class ViewModel : INotifyPropertyChanged {
public ViewModel() {
this.values = new ObservableCollection<string>()
{
"first",
"second",
"third"
};
}
ObservableCollection<string> values;
string selectedValue;
public ObservableCollection<string> Values { get { return values; } }
public string SelectedValue {
get { return selectedValue; }
set {
if (Equals(selectedValue, values))
return;
selectedValue = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedValue"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So, we have data source, selected value and we'll bind it like this:
<StackPanel>
<TreeView ItemsSource="{Binding Values}"
local:BindingWrapper.Source="{Binding SelectedItem, RelativeSource={RelativeSource Self}, Mode=OneWay}"
local:BindingWrapper.Target="{Binding SelectedValue, Mode=OneWayToSource}"
>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Header" Value="{Binding}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<TextBlock Text="{Binding SelectedValue}"/>
</StackPanel>
In the TreeView bound to the ItemsSource from the ViewModel I've created two bindings so they are changing the SelectedValue property in your ViewModel. TextBlock in the end of the sample is used just to show that this approach works.
About the very clean MVVM - I think that it is not the same as the "no code-behind". In my sample the ViewModel still doesn't know anything about your view and if you'll use another control to show your data e.g. ListBox you will be able to use the simple two-way binding and the "BindingWrapper" helper will not make your code unreadable or unportable or anything else.
Create a SelectedSeason property in your ViewModel and bind the ListView's ItemsSource to SelectedSeason.Episodes.
In a perfect world, you could now use a Two-Way binding in the TreeView to automatically update this property when the SelectedItem changes. However, the TreeView's SelectedItem property is readonly and cannot be bound. You can use just a little bit of code-behind and create an event handler for the SelectionChanged event of the TreeView to update your ViewModel's SelectedSeason there. IMHO this doesn't violate the the MVVM principles.
If you want a pure XAML solution, that a look at this answer.

UserControl using parent elements in wpf?

When you have a usercontrol in wpf can it reach outside to its parent elements? For instance my user control just lists some generic things which are held inside the control which is encapsulated within a dockpanel on the main window, but I have a textbox and button in the main window that I would like to access from the control... is this possible?
It would save me alot of time rather than changing the content of the entire window and displaying the same textbox/button in every usercontrol. If anyone has an example of this it would be much appreciated.
Yes it is possible and here is some code I have used to compose presentations out of UserControls that have DPs.
I don't love it even a little, but it works. I also think this is a great topic and maybe some code will help get some better answers!
Cheers,
Berry
UserControl XAML
<Button x:Name="btnAddNewItem" Style="{StaticResource blueButtonStyle}" >
<StackPanel Orientation="Horizontal">
<Image Source="{resx:Resx ResxName=Core.Presentation.Resources.MasterDetail, Key=bullet_add}" Stretch="Uniform" />
<Label x:Name="tbItemName" Margin="5" Foreground="White" Padding="10, 0">_Add New [item]</Label>
</StackPanel>
</Button>
UserControl Code Behind
public partial class AddNewItemButton : UserControl
{
...
#region Item Name
public static readonly DependencyProperty ItemNameProperty = DependencyProperty.Register(
"ItemName", typeof(string), typeof(AddNewItemButton),
new FrameworkPropertyMetadata(OnItemNameChanged));
public string ItemName
{
get { return (string)GetValue(ItemNameProperty); }
set { SetValue(ItemNameProperty, value); }
}
public string ButtonText { get { return (string) tbItemName.Content; } }
private static void OnItemNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
// When the item name changes, set the text of the item name
var control = (AddNewItemButton)obj;
control.tbItemName.Content = string.Format(GlobalCommandStrings.Subject_Add, control.ItemName.Capitalize());
control.ToolTip = string.Format(GlobalCommandStrings.Subject_Add_ToolTip, control.ItemName);
}
#endregion
#region Command
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(AddNewItemButton),
new FrameworkPropertyMetadata(OnCommandChanged));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
private static void OnCommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
// When the item name changes, set the text of the item name
var control = (AddNewItemButton)obj;
control.btnAddNewItem.Command = control.Command;
}
#endregion
}
Another UserControl showing Composition
<UserControl ...
xmlns:uc="clr-namespace:Smack.Core.Presentation.Wpf.Controls.UserControls"
>
<DockPanel LastChildFill="True">
...
<uc:AddNewItemButton x:Name="_addNewItemButton" Margin="0,0,10 0" DockPanel.Dock="Right" />
...
</DockPanel>
</UserControl>
A better design pattern would be to have the usercontrol notify (via event) the main window when something needs to be changed, and to ask the window (via method) when it needs some information. You would, for example, have a GetText() method on the window that the usercontrol could call, and a ChangeText event on the usercontrol that the window would subscribe to.
The idea is to keep the window in control at all times. Using this mentality will make it easier for you to develop applications in the future.
To answer your question: yes, you can either access parent controls either through a RelativeSource binding or through the Parent member in the back code. But a better answer is similar to #KendallFrey answer. Adopt a framework like Light MVVM and use its messenger class or use events the way Kendall described.

Categories

Resources