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.
Related
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);
}
}
I'm trying to create a custom UserControl that displays the properties of a complex object as a form. Additionally, if the user unchecks a checkbox in the header of the UserControl, the value of the dependency property should be null (but the form values should stay displayed, even if the form is disabled) - and vice versa.
I'm working with two dependency properties of type ComplexObject on the UserControl - one is public (this will be bound to by the client), another is private (its properties will be bound to the internal controls in the UserControl):
public ComplexObject ComplexObject
{
get { return (ComplexObject )GetValue(ComplexObjectProperty); }
set { SetValue(ComplexObjectProperty, value); }
}
private ComplexObject VisibleComplexObject
{
get { return (ComplexObject)GetValue(VisibleComplexObjectProperty); }
set { SetValue(VisibleComplexObjectProperty, value); }
}
Now I'm struggling with a binding between those two, so that CompexObject becomes either VisibleComplexObject or null based on the checkbox value. This should also work the other way. I've tried to solve this using DataTriggers in the Style of the UserControl, but was unable to do so:
<UserControl.Style>
<Style TargetType="local:CheckableComplexTypeGroup">
// 'CheckableComplexTypeGroup' TargetType does not match type of the element 'UserControl'
</Style>
</UserControl.Style>
Using <local:CheckableComplexTypeGroup.Style> instead of <UserControl.Style> didn't work either.
Are there any other suggestions? Or maybe another way of doing this?
Finally I've solved this without using plain old event handlers instead of binding/triggers.
CheckableComplexObjectGroup.xaml:
<UserControl Name="thisUC" ...>
<GroupBox>
<GroupBox.Header>
<CheckBox Name="cbComplexObject"
IsChecked="{Binding ElementName=thisUC, Path=ComplexObject, Mode=OneWay, Converter={StaticResource nullToFalseConv}}"
Checked="cbComplexObject_Checked" Unchecked="cbComplexObject_Unchecked"/>
</GroupBox.Header>
...
</UserControl>
CheckableComplexObjectGroup.cs:
public static readonly DependencyProperty ComplexObjectProperty =
DependencyProperty.Register("ComplexObject",
typeof(ComplexObject),
typeof(CheckableComplexObjectGroup),
new PropertyMetadata(null));
private static readonly DependencyProperty VisibleComplexObjectProperty =
DependencyProperty.Register("VisibleComplexObject",
typeof(ComplexObject),
typeof(CheckableComplexObjectGroup),
new PropertyMetadata(new ComplexObject()));
//...
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == ComplexObjectProperty)
{
if (null != e.NewValue)
VisibleComplexObject = ComplexObject;
}
base.OnPropertyChanged(e);
}
private void cbComplexObject_Checked(object sender, RoutedEventArgs e)
{
ComplexObject = VisibleComplexObject;
}
private void cbComplexObject_Unchecked(object sender, RoutedEventArgs e)
{
ComplexObject = null;
}
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.
If I change StaysOpen to "True", the popup shows up, but it doesn't close when you click outside of it, so that's not what I want.
Here is the relevant XAML code:
<Border x:Name="popupPlacementTarget">
<i:Interaction.Behaviors>
<local:PopupBehavior>
<local:PopupBehavior.Popup>
<Popup PlacementTarget="{Binding ElementName=popupPlacementTarget}"
Placement="MousePoint"
StaysOpen="False">
<ContentPresenter Content="{Binding SomeContent}" ContentTemplate="{StaticResource SomeContentTemplate}" />
</Popup>
<local:PopupBehavior.Popup>
</local:PopupBehavior>
</i:Interaction.Behaviors>
</Border>
And here is the PopupBehavior code:
public class PopupBehavior : Behavior<UIElement>
{
#region Dependency Properties
public static readonly DependencyProperty PopupProperty = DependencyProperty.Register(
"Popup", typeof(Popup), typeof(PopupBehavior), new FrameworkPropertyMetadata(default(Popup)));
#endregion Dependency Properties
#region Properties
public Popup Popup
{
get { return (Popup)this.GetValue(PopupProperty); }
set { this.SetValue(PopupProperty, value); }
}
#endregion Properties
#region Protected Methods
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.MouseDown += this.OnMouseDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.MouseDown -= this.OnMouseDown;
}
#endregion Protected Methods
#region Private Methods
private void OnMouseDown(object sender, MouseButtonEventArgs eventArgs)
{
var popup = this.Popup;
if (popup == null)
{
return;
}
this.Popup.IsOpen = true;
}
#endregion Private Methods
}
Any idea why my popup won't show up with StaysOpen="False"?
It turns out there was an ancestor that was indiscriminately grabbing capture on mouse down. Everything is working correctly now that I've corrected this issue.
As an aside, I was able to hack around this problem by setting StaysOpen="True", inheriting Popup, and hooking in to the global mouse events within the derived Popup. When the popup opens, I'd attach the handler to the global input events. Then, when an event is received, I'd filter it such that I'm only responding to left mouse button down events. In handling this event, I'd close the popup and detach the event if the mouse is not hovering the popup. It worked, but it is obviously a dirty hack, and I'm happy that I got this working without it.
I'm trying to do the following thing:
I have a TabControl with several tabs.
Each TabControlItem.Content points to PersonDetails which is a UserControl
Each BookDetails has a dependency property called IsEditMode
I want a control outside of the TabControl , named ToggleEditButton, to be updated whenever the selected tab changes.
I thought I could do this by changing the ToggleEditButton data context, by it doesn't seem to work (but I'm new to WPF so I might way off)
The code changing the data context:
private void tabControl1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.Source is TabControl)
{
if (e.Source.Equals(tabControl1))
{
if (tabControl1.SelectedItem is CloseableTabItem)
{
var tabItem = tabControl1.SelectedItem as CloseableTabItem;
RibbonBook.DataContext = tabItem.Content as BookDetails;
ribbonBar.SelectedTabItem = RibbonBook;
}
}
}
}
The DependencyProperty under BookDetails:
public static readonly DependencyProperty IsEditModeProperty =
DependencyProperty.Register("IsEditMode", typeof (bool), typeof (BookDetails),
new PropertyMetadata(true));
public bool IsEditMode
{
get { return (bool)GetValue(IsEditModeProperty); }
set
{
SetValue(IsEditModeProperty, value);
SetValue(IsViewModeProperty, !value);
}
}
And the relevant XAML:
<odc:RibbonTabItem Title="Book" Name="RibbonBook">
<odc:RibbonGroup Title="Details" Image="img/books2.png" IsDialogLauncherVisible="False">
<odc:RibbonToggleButton Content="Edit"
Name="ToggleEditButton"
odc:RibbonBar.MinSize="Medium"
SmallImage="img/edit_16x16.png"
LargeImage="img/edit_32x32.png"
Click="Book_EditDetails"
IsChecked="{Binding Path=IsEditMode, Mode=TwoWay}"/>
...
There are two things I want to accomplish, Having the button reflect the IsEditMode for the visible tab, and have the button change the property value with no code behind (if posible)
Any help would be greatly appriciated.
You can accomplish what you want by binding directly to the TabControl's SelectedItem using the ElementName binding:
<odc:RibbonTabItem Title="Book" Name="RibbonBook">
<odc:RibbonGroup Title="Details" Image="img/books2.png" IsDialogLauncherVisible="False">
<odc:RibbonToggleButton Content="Edit"
Name="ToggleEditButton"
odc:RibbonBar.MinSize="Medium"
SmallImage="img/edit_16x16.png"
LargeImage="img/edit_32x32.png"
Click="Book_EditDetails"
IsChecked="{Binding ElementName=myTabControl, Path=SelectedItem.IsEditMode, Mode=TwoWay}"/>
Where myTabControl is the name of the TabControl (the value of the x:Name property). You shouldn't need to handle the SelectionChanged event anymore to update the DataContext of the button.