After getting the animation on an image to run
which I got from here: Animate image in a button
I want to be able to switch the animation on and off, depending
on a button click from outside, i.e. from the ViewModel
So I added a new DependencyProperty to the Bahavior (with all those things that are needed here)
public static readonly DependencyProperty IsShakingProperty =
DependencyProperty.Register(IsShakingName,
typeof(bool),
typeof(ShakeBehavior),
new PropertyMetadata(DefaultIsShaking));
I have added a new public property to my ViewModel
public bool IsShaking { get; set; }
But what can I do to switch the animation on and off, depending on the
ViewModel property set to true or false? (I want to control the animation
on a button click)
Here is some of the code of which i think it is relevant
private Timeline CreateAnimationTimeline()
{
DoubleAnimationUsingKeyFrames animation = new DoubleAnimationUsingKeyFrames();
animation.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("(0).(1)", UIElement.RenderTransformProperty, RotateTransform.AngleProperty));
int keyFrameCount = 8;
double timeOffsetInSeconds = 0.25;
double totalAnimationLength = keyFrameCount * timeOffsetInSeconds;
double repeatInterval = RepeatInterval;
bool isShaking = IsShaking;
// Can't be less than zero and pointless to be less than total length
if (repeatInterval < totalAnimationLength)
repeatInterval = totalAnimationLength;
animation.Duration = new Duration(TimeSpan.FromSeconds(repeatInterval));
int targetValue = 12;
for (int i = 0; i < keyFrameCount; i++)
animation.KeyFrames.Add(new LinearDoubleKeyFrame(i % 2 == 0 ? targetValue : -targetValue, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(i * timeOffsetInSeconds))));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(totalAnimationLength))));
return animation;
}
Here is the part of my XAML:
<ListBox.ItemTemplate>
<DataTemplate>
<Button Focusable="False" Command="{Binding ClickToolCommand}" Grid.Row="{Binding Path=Row}" Grid.Column="{Binding Path=Col}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0">
<Image Source="myImage.png" Grid.Row="{Binding Path=Row}" Grid.Column="{Binding Path=Col}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0">
<i:Interaction.Behaviors>
<local:ShakeBehavior RepeatInterval="1" SpeedRatio="3.0" IsShaking="{Binding Path=IsShaking}"/>
</i:Interaction.Behaviors>
</Image>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
Perhaps a DataTrigger can help, as pointed out in other SOs, but I do not have a storyboard inside my XAML, as I have a custom Behavior
Any input highly appreciated!
Firstly, let me repeat what I said in the original post that started this whole thing. Doing a "shaky" image button becomes a whole lot simpler if you just use a custom control. Also, using a shaky image button as "a way to engage a users attention" is a horrible idea that reminds me of 1990's website design. In addition, there is a small flaw in the implementation you copied, there is no exit action on the trigger that was created. Regardless, here is how to do what you require:
Create an attached property as follows:
public static bool GetStopAnimating(DependencyObject obj)
{
return (bool)obj.GetValue(StopAnimatingProperty);
}
public static void SetStopAnimating(DependencyObject obj, bool value)
{
obj.SetValue(StopAnimatingProperty, value);
}
// Using a DependencyProperty as the backing store for StopAnimating. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StopAnimatingProperty =
DependencyProperty.RegisterAttached("StopAnimating", typeof(bool), typeof(ShakeBehavior), new UIPropertyMetadata(true));
Then replace the existing "OnAttach" method with the folllowing:
private BeginStoryboard _beginStoryBoard;
private RemoveStoryboard _removeStoryboard;
protected override void OnAttached()
{
_orignalStyle = AssociatedObject.Style;
_beginStoryBoard = new BeginStoryboard { Storyboard = CreateStoryboard() };
_beginStoryBoard.Name = "terribleUi";
_removeStoryboard = new RemoveStoryboard();
_removeStoryboard.BeginStoryboardName = _beginStoryBoard.Name;
AssociatedObject.Style = CreateShakeStyle();
AssociatedObject.Style.RegisterName("terribleUi", _beginStoryBoard);
}
Then instead of having the shaking trigger based off the visibility of the button, change it to work off your attached property:
private Trigger CreateTrigger()
{
Trigger trigger = new Trigger
{
Property = StopAnimatingProperty,
Value = false,
};
trigger.EnterActions.Add(_beginStoryBoard);
trigger.ExitActions.Add(_removeStoryboard);
return trigger;
}
Then you use it as follows:
<Button Height="50" Width="150" >
<StackPanel>
<Image Source="\Untitled.png" local:ShakeBehavior.StopAnimating="{Binding YourPropertyToStopTheShaking}">
<i:Interaction.Behaviors>
<local:ShakeBehavior RepeatInterval="5.0" SpeedRatio="3.0" />
</i:Interaction.Behaviors>
</Image>
</StackPanel>
</Button>
Related
Faced the need to select a fragment of text in TextBlock, namely certain keywords on which the ListBox was filtered, this text block itself and containing
XAML variant, title property is not bound
<ListBox Name="ProcedureList" ItemsSource="{Binding Path=ProceduresView.View}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="ProcedurePanel" PreviewMouseDown="ProcedurePanel_OnPreviewMouseDown">
<DockPanel Width="{c:Binding ElementName=MainPanel, Path=Width-40}">
<!--<TextBlock Name="MainText" TextWrapping="Wrap" FontSize="16" Text="{Binding Path=title}" HorizontalAlignment="Left" />-->
<htb:HighlightTextBlock Name="MainText" TextWrapping="Wrap" FontSize="16" Text="{Binding Path=title}" HorizontalAlignment="Left">
<htb:HighlightTextBlock.HighlightRules>
<htb:HighlightRule
IgnoreCase="{Binding IgnoreCase, Source={StaticResource SourceVm}}"
HightlightedText="{Binding Path=title, Converter={StaticResource getFilter}}">
<htb:HighlightRule.Highlights>
<htb:HighlightBackgroung Brush="Yellow"/>
</htb:HighlightRule.Highlights>
</htb:HighlightRule>
</htb:HighlightTextBlock.HighlightRules>
</htb:HighlightTextBlock>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
A component written by our compatriot with open source is used
Component
Description of component
The commented code is an old TexBlock with no selection
The new HighlightTextBlock component perfectly selects the text if you use a static resource, as in the example, but when I try to bind it to the current text it can not find this field :(, I'm new in WPF help figure it out
HightlightedText="{Binding Path=title, Converter={StaticResource getFilter}}"
How correctly to anchor this property to title?
DataContext structure
public ObservableCollection<Procedure> Procedures { set; get; }
public CollectionViewSource ProceduresView { set; get; } = new CollectionViewSource();
....
Procedures = new ObservableCollection<Procedure>();
ProceduresView.Filter += Procedures_Filter;
ProceduresView.Source = Procedures;
....
public class Procedure : ObservableObject
{
....
public String title { get; set; }
....
}
....
// Simple filtering
void Procedures_Filter(object sender, FilterEventArgs e)
{
Procedure procedure = (Procedure) e.Item;
Boolean flag = false;
if (!string.IsNullOrEmpty(filter))
{
Setting.Filter sfilter = new Setting.Filter();
sfilter.type = "искать везде";
sfilter.text = filter;
ObservableCollection<Setting.Filter> arr = new ObservableCollection<Setting.Filter>();
arr.Add(sfilter);
if (Utils.AssignedProcedureFromFilter(procedure, arr)) flag = true;
}
else flag = true;
e.Accepted = flag;
}
Video with problem description
Simplified project emitting my functional
On the Russian-speaking forum they explained to me that:
Your case, in fact, is more serious. DataContext you, apparently, the
right one. But your Binding expression is inside the HighlightRules
property setter, which is not part of the visual tree (because it is
not available as a Child element of your control). And elements that
are not inside the visual tree, participate in bindings are only
limited: they do not inherit DataContext, nor access by name through
ElementName. As a solution, bind to an element via x: Reference. In my
(heavily cut) test case, HightlightedText = "{Binding Path =
DataContext.title, Source = {x: Reference MainText}} is triggered."
But, if directly replaced by this, a strange error works: 'Can not
call MarkupExtension. ProvideValue because of a cyclic dependency. The
properties inside the MarkupExtension can not reference objects that
reference the MarkupExtension result.
The workaround for the error was found here: you need to put your element in resources. We get this:
XAML, modified according to the recommendations
<ListBox Name="ProcedureList" ItemsSource="{Binding Path=ProceduresView.View}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="ProcedurePanel" PreviewMouseDown="ProcedurePanel_OnPreviewMouseDown">
<DockPanel Width="{c:Binding ElementName=MainPanel, Path=Width-40}">
<!--<TextBlock Name="MainText" TextWrapping="Wrap" FontSize="16" Text="{Binding Path=title}" HorizontalAlignment="Left" />-->
<htb:HighlightTextBlock Name="MainText" TextWrapping="Wrap" FontSize="16"
Text="{Binding Path=title}" HorizontalAlignment="Left">
<htb:HighlightTextBlock.Resources>
<htb:HighlightRule x:Key="HR"
IgnoreCase="{Binding IgnoreCase, Source={StaticResource SourceVm}}"
HightlightedText="{Binding Path=DataContext.title, Source={x:Reference MainText}, Converter={StaticResource getFilter}}">
<htb:HighlightRule.Highlights>
<htb:HighlightBackgroung Brush="Yellow"/>
</htb:HighlightRule.Highlights>
</htb:HighlightRule>
</htb:HighlightTextBlock.Resources>
<htb:HighlightTextBlock.HighlightRules>
<htb:HighlightRulesCollection>
<StaticResource ResourceKey="HR"/>
</htb:HighlightRulesCollection>
</htb:HighlightTextBlock.HighlightRules>
</htb:HighlightTextBlock>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I was given advice on the restructuring of XAML, through resources, this partially solved the problem (I successfully got the title text in the converter), but the element ceased to perform its functions (allocation) During the discussion, it was suggested that the component itself should be finalized
#iRumba: In theory, the whole trick should not be necessary if you put
the HighlighRule collection (also) in a visual tree. Then the
DataContext will be automatically inherited and on idea the binding
through ElementName too will work.
#iRumba: I do not remember exactly. It seems, it is necessary to
specify to add all HighlightRule as LogicalChildren (for this purpose
on idea it is necessary to redefine protected internal override
IEnumerator LogicalChildren). This is a complicated, advanced
technique, yes.
Sorry for Google Translator
Found a solution
public class SearchHightlightTextBlock : TextBlock
{
public SearchHightlightTextBlock() : base() { }
public String SearchText
{
get { return (String)GetValue(SearchTextProperty); }
set { SetValue(SearchTextProperty, value); }
}
private static void OnDataChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
TextBlock tb = (TextBlock)source;
if (tb.Text.Length == 0)
return;
string textUpper = tb.Text.ToUpper();
String toFind = ((String)e.NewValue).ToUpper();
int firstIndex = textUpper.IndexOf(toFind);
String firstStr = "";
String foundStr = "";
if (firstIndex != -1)
{
firstStr = tb.Text.Substring(0, firstIndex);
foundStr = tb.Text.Substring(firstIndex, toFind.Length);
}
String endStr = tb.Text.Substring(firstIndex + toFind.Length,
tb.Text.Length - (firstIndex + toFind.Length));
tb.Inlines.Clear();
tb.FontSize = 16;
var run = new Run();
run.Text = firstStr;
tb.Inlines.Add(run);
run = new Run();
run.Background = Brushes.Yellow;
run.Text = foundStr;
tb.Inlines.Add(run);
run = new Run();
run.Text = endStr;
tb.Inlines.Add(run);
}
public static readonly DependencyProperty SearchTextProperty =
DependencyProperty.Register("SearchText",
typeof(String),
typeof(SearchHightlightTextBlock),
new FrameworkPropertyMetadata(null, OnDataChanged));
}
Use
<parser:SearchHightlightTextBlock SearchText="{Binding Path=title, Converter={StaticResource getFilter}}" Text="{Binding title}"/>
In button flyout I am using one usercontrol inside that I have textbox. when running the app the textbox is appearing as readonly, don't know why I am getting this issue. nowhere I am setting readonly.
<TextBox Margin="2" Height="32"
MaxHeight="60"
TextWrapping="Wrap"
HorizontalAlignment="Stretch"
TextAlignment="Left"
Text="ramesh"
Style="{x:Null}"/>
Figure out the issue it's because of anniversary update.
https://blogs.msdn.microsoft.com/wsdevsol/2016/09/14/combobox-from-an-appbarbutton-loses-mouse-input-on-1607/
I created attached property for solution given in above link. Below is the attached property
public class CompatExtensions
{
public static bool GetAllowFocusOnInteraction(DependencyObject obj)
{
return (bool)obj.GetValue(AllowFocusOnInteractionProperty);
}
public static void SetAllowFocusOnInteraction(DependencyObject obj, bool value)
{
obj.SetValue(AllowFocusOnInteractionProperty, value);
}
// Using a DependencyProperty as the backing store for AllowFocusOnInteraction.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty AllowFocusOnInteractionProperty =
DependencyProperty.RegisterAttached("AllowFocusOnInteraction",
typeof(bool),
typeof(CompatExtensions),
new PropertyMetadata(0, AllowFocusOnInteractionChanged));
private static bool allowFocusOnInteractionAvailable =
Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent(
"Windows.UI.Xaml.FrameworkElement",
"AllowFocusOnInteraction");
private static void AllowFocusOnInteractionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (allowFocusOnInteractionAvailable)
{
var element = d as FrameworkElement;
if (element != null)
{
element.AllowFocusOnInteraction = (bool)e.NewValue;
}
}
}
}
And an example of it used:
<AppBarButton local:CompatExtensions.AllowFocusOnInteraction="True" Icon="Setting">
<AppBarButton.Flyout>
<Flyout>
<StackPanel Orientation="Vertical" >
<ComboBox>
<ComboBoxItem Content="Red" IsSelected="True" />
<ComboBoxItem Content="Green" />
<ComboBoxItem Content="Blue"/>
</ComboBox>
</StackPanel>
</Flyout>
</AppBarButton.Flyout>
</AppBarButton>
Difficult to be sure about any answer given how the few details have been provided but I once saw something similar due to sizing of the TextBox. The UWP text box has a "delete" button (a small cross) at the end of the box to delete the current content. When the TextBox was sized vertically, the delete button scaled to take up the entirety of the TextBox thereby making it look read only.
If you're facing a similar issue, try setting AcceptsReturn="True" and InputScope="Text" on the TextBox.
I wrote a custom templated RichTextBlock in order to represent the emojis and links as images and hyperlink buttons respectivly. It basically has only one "Text" dependency property because all the conversion works will process inside the control when it recieve the plain text/string.
The problem is if the Text property is with some specific string like Text="asdasdsa", the control will represent "asdasdas" properly. If the property is set to bind something like Text="{Binding something}", the control won't work and the setter Text dependency property will never fire.
The style of the control is basically like this
<Style TargetType="local:MyRichTextBlock" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyRichTextBlock">
<Border HorizontalAlignment="Center" Width="auto" Height="auto">
<StackPanel>
<RichTextBlock x:Name="ChildRichTextBlock"/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Basically, it converts the plain text and replace those emojis and hyperlinks in XAML, at the end add the block of XAML code into the RichTextBlock as a paragraph.
The code of the control
public sealed class MyRichTextBlock : Control
{
RichTextBlock _richTextBlock;
StringBuilder builder = new StringBuilder();
Regex urlRx = new Regex(#"(?<url>(http:[/][/]|www.)([a-z]|[A-Z]|[0-9]|[/.]|[~])*)", RegexOptions.IgnoreCase);
public MyRichTextBlock()
{
this.DefaultStyleKey = typeof(MyRichTextBlock);
foreach (var key in emojiDict.Keys)
{
builder.Append(key.Replace("[", #"\[").Replace("]", #"\]"));
builder.Append("|");
}
builder.Remove(builder.Length - 1, 1);
}
private readonly Dictionary<string, string> emojiDict = new Dictionary<string, string>
{
//Dictionary of emojis key
};
protected override void OnApplyTemplate()
{
_richTextBlock = GetTemplateChild("ChildRichTextBlock") as RichTextBlock;
SetRichTextBlock(Text);
}
public String Text
{
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); } //This setter won't fire
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(MyRichTextBlock), new PropertyMetadata(""));
//Conversion work, it does not really related to the property I think
private void SetRichTextBlock(string value)
{
string abc = value;
MatchCollection matches = urlRx.Matches(value);
var r = new Regex(builder.ToString());
var mc = r.Matches(value);
foreach (Match m in mc)
{
value = value.Replace(m.Value, string.Format(#"<InlineUIContainer><Border><Image Source=""ms-appx:///Assets/Emoji/{0}.png"" Margin=""2,0,2,0"" Width=""30"" Height=""30""/></Border></InlineUIContainer>", emojiDict[m.Value]));
}
foreach (Match match in matches)
{
string url = match.Groups["url"].Value;
value = value.Replace(url,
string.Format("<InlineUIContainer><Border><HyperlinkButton Margin=\"0,0,0,-4\" Padding=\"0,2,0,0\" NavigateUri =\"{0}\"><StackPanel HorizontalAlignment=\"Center\" Height=\"25\" Width=\"90\" Background=\"#FFB8E9FF\" Orientation = \"Horizontal\"><Image Margin=\"5,0,0,0\" Source = \"/Assets/Link.png\" Width = \"15\" Height = \"15\"/><TextBlock Margin=\"4,2.5,0,0\" Text=\"网页链接\" Foreground=\"White\" FontFamily=\"Microsoft YaHei UI\" FontSize=\"14\" FontWeight=\"Bold\"/></StackPanel ></HyperlinkButton></Border></InlineUIContainer>", url));
}
value = value.Replace("\r\n", "<LineBreak/>");
var xaml = string.Format(#"<Paragraph
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
<Paragraph.Inlines>
<Run></Run>
{0}
</Paragraph.Inlines>
</Paragraph>", value);
var p = (Paragraph)XamlReader.Load(xaml);
_richTextBlock.Blocks.Add(p);
}
}
And the way how I use this control
<DataTemplate x:Name="NormalTemplate">
<Grid Grid.Column="1"
Grid.Row="1"
d:LayoutOverrides="LeftMargin, RightMargin, TopMargin, BottomMargin"
Margin="0,15,0,15"
Name="NormalTemplateGrid">
//Codes omit//
<local:MyRichTextBlock x:Name="WeiboTextTextblock"
Margin="20,35,0,0"
Text="{Binding Text}"
Grid.Column="2"
Grid.Row="1"
d:LayoutOverrides="HorizontalAlignment, TopMargin, BottomMargin, LeftPosition, RightPosition, TopPosition, BottomPosition"
HorizontalAlignment="Left"
FontFamily="Microsoft YaHei"/>
</Grid>
</DataTemplate>
Any ideas what is the problem and what is the solution for this?
The setter is there just for the sake of completing a dependency property. You need to define a property changed callback for your Text property, like this -
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(nameof(Text), typeof(String), typeof(MyRichTextBlock), new PropertyMetadata("", OnTextChange));
private static void OnTextChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var rtb = (MyRichTextBlock)d;
rtb.SetRichTextBlock(e.NewValue.ToString());
}
I have a Control that displays something (let's call it Display). In this control I have a class Camera that stores things like zoom, position and rotation.
I can change the zoom from an external control (let's call it ZoomBar).
Now I had the idea to connect all of them with a TwoWay-Binding like this:
ZoomBar.Value <--> Display.Zoom <--> Camera.Zoom
It should be like: ZoomBar value changes --> update Display.Zoom --> update Camera.Zoom. Display.Zoom does not really do something. It's only for exchange the data between Camera and ZoomBar.
But I get nothing. After a short check in the Camera:
public float Zoom
{
get { MessageBox.Show("Any calls here?"); return (float)GetValue(ZoomProperty); }
set { ... }
}
I get a massive amount of MessageBoxes. I guess there is something like loop in there. Like ZoomBar.Value --> Display.Zoom --> ZoomBar.Value --> ...
My question
Are the two-way bindings causing the problem and if it is the bindings, is there a XAML way to fix this?
XAML ZoomBar
<StatusBarItem Title="Zoom Bar" HorizontalAlignment="Right">
<Slider x:Name="uxInputZoom" Style="{DynamicResource ZoomSliderStyle}" Value="100" Maximum="500" Minimum="20" />
</StatusBarItem>
XAML Display
<Display x:Name="uxDisplay" Zoom="{Binding Value, Converter={StaticResource PercentToFractionConverter}, ElementName=uxInputZoom, Mode=TwoWay}" />
Code Display
public static readonly DependencyProperty ZoomProperty = DependencyProperty.Register("Zoom", typeof(float), typeof(Display), new FrameworkPropertyMetadata(1f));
public float Zoom
{
get { return (float)GetValue(ZoomProperty); }
set { SetValue(ZoomProperty, value); }
}
Camera _camera = new Camera();
//...
public Display()
{
Binding binding = new Binding("Zoom");
binding.Source = _camera;
binding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(this, Display.ZoomProperty, binding);
InitializeComponent();
...
}
You certainly can data bind one property value to more than one UI control. Take this simple example which enables movements of the Slider to update the value in the TextBox, while also enabling values entered in the TextBox to update the Slider.Value property:
<StackPanel>
<Slider Value="{Binding Width2}" Minimum="0.0" Maximum="100.0" Margin="0,0,0,20" />
<TextBox Text="{Binding Width2, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
This will not cause any feedback loops as in your code, so I suspect that you have something else doing that.
I've been working on a small 3D preview window in a MVVM style application... The view is created then its data context is set. Therefore it seems that ZoomExtentsWhenLoaded="True" doesn't seem to help do what I need. I need something like, ZoomExtentsWhenDataContextChanges.
Interestingly, I've found that if I use a mouse gesture like the one defined below, I can physically click on the HelixViewport3D and it will perform a ZoomExtents.
HelixViewport3D.ZoomExtentsGesture = new MouseGesture(MouseAction.LeftDoubleClick);
However, if do something like this...
HelixViewport3D.DataContextChanged += (o, e) => ResetCamera();
private void ResetCamera()
{
var dc = HelixViewport3D.DataContext as WellSurveyPlot3DViewModel;
HelixViewport3D.ResetCamera();
HelixViewport3D.Camera = dc.PerspectiveCamera;
HelixViewport3D.ZoomExtents();
}
The viewport does zoom, it just doesn't center itself, like it does when activating ZoomExtents when using the mouse gesture.
I tried ResetCamera, and several other things... What is the standard way of dealing with keeping a viewport around and swapping out the DataContext instead of creating a new one each time?
I fixed this with an attached property. I read through the HelixViewport3D source code and got this idea, after noticing how the camera works. It seems an update to the default camera through a property binding doesn't really do anything after the control is initialized.
public static class HelixViewport3DZoomExtent
{
private static readonly Type OwnerType = typeof(HelixViewport3DZoomExtent);
public static readonly DependencyProperty ZoomExtentsOnUpdateProperty = DependencyProperty.RegisterAttached("ZoomExtentsOnUpdate", typeof(bool), OwnerType, new PropertyMetadata(false, OnDataContextChanged));
public static bool GetZoomExtentsOnUpdate(DependencyObject obj)
{
return (bool)obj.GetValue(ZoomExtentsOnUpdateProperty);
}
public static void SetZoomExtentsOnUpdate(DependencyObject obj, bool value)
{
obj.SetValue(ZoomExtentsOnUpdateProperty, value);
}
private static void OnDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var viewport = d as HelixViewport3D;
if (viewport == null) return;
if (viewport.DataContext == null) return;
viewport.Camera = viewport.DefaultCamera;
viewport.ZoomExtents();
}
}
Here is the Xaml
<Border BorderBrush="Black" BorderThickness="1">
<Grid>
<h:HelixViewport3D Name="HelixViewport3D"
PanGesture="LeftClick"
DataContext="{Binding PreviewPlot, UpdateSourceTrigger=PropertyChanged}"
DefaultCamera="{Binding PerspectiveCamera, UpdateSourceTrigger=PropertyChanged}"
services:HelixViewport3DZoomExtent.ZoomExtentsOnUpdate="{Binding RelativeSource={RelativeSource AncestorType={x:Type views:WellSurveyPlot3DPreview}},
Path=DataContext.PreviewUpdatedReZoom, UpdateSourceTrigger=PropertyChanged}">
<h:SunLight/>
<h:TubeVisual3D Path="{Binding TubePath}" Diameter="75" ThetaDiv="12" IsPathClosed="False" Fill="LightGray"/>
<h:GridLinesVisual3D Width="{Binding GridLength}" Length="{Binding GridLength}" MajorDistance="{Binding MajorGridLines}" Thickness="25"
MinorDistance="{Binding MajorGridLines, UpdateSourceTrigger=PropertyChanged}" LengthDirection="1,0,0" Normal="0,0,1"
Center="{Binding BottomPlaneCenter,UpdateSourceTrigger=PropertyChanged}" Fill="Red" />
<h:GridLinesVisual3D Width="{Binding GridLength}" Length="{Binding GridLength, UpdateSourceTrigger=PropertyChanged}" LengthDirection="0,0,1" Normal="1,0,0" Thickness="25"
MajorDistance="{Binding MajorGridLines}" MinorDistance="{Binding MajorGridLines}"
Center="{Binding BackLeftPlaneCenter, UpdateSourceTrigger=PropertyChanged}" Fill="Blue" />
<h:GridLinesVisual3D Width="{Binding GridLength}" Length="{Binding GridLength, UpdateSourceTrigger=PropertyChanged}" LengthDirection="1,0,0" Normal="0,1,0" Thickness="25"
MajorDistance="{Binding MajorGridLines}" MinorDistance="{Binding MajorGridLines}"
Center="{Binding BackRightPlaneCenter,UpdateSourceTrigger=PropertyChanged}" Fill="Green" />
</h:HelixViewport3D>
<Button Content="Open Well Viewer" HorizontalAlignment="Left" VerticalAlignment="Top" Command="{Binding OpenWindowCmd}"/>
</Grid>
</Border>
In my view model I have to toggle my PreviewUpdateReZoom property.
private void LoadSurveyPoints(List<WellSurveyPointCalculated> surveyPoints)
{
_coordinatesCalculator = _calcGlobalCoordsFactory.Create(surveyPoints);
_wellXyzCoordinates = _coordinatesCalculator.PlotGlobalCoordinates(100).ToList();
PreviewPlot = WellSurveyPlot3DViewModel();
PreviewUpdatedReZoom = false;//Toggle true false to send property changed and get attached property to fire.
PreviewUpdatedReZoom = true;
}
This now works such that every new item drawn into the viewport has the correct camera settings and zooms to extents...