Animating Hub's scrolling - xaml

I've written an Attached Property to be able to bind the index of the displayed HubSection to my ViewModel, so that I can change it within my code.
I'm using the Hub's ScrollTo(section) method but it doesn't animate the scroll, so I decided to use the ScrollViewer inside the Hub with the ChangeView method.
What happens is that, having 4 HubSection, it just scrolls to the second one before hanging.
The viewer.HorizontalOffset stays fixed at 360 (which is the HubSection's ActualWidth) and there's no way to change it!
Here's the code that I'm using to animate the scroll:
async private static Task ScrollHubToSection(Hub hub, HubSection section, int index)
{
var dispatcher = hub.Dispatcher;
var viewer = hub.GetDescendantsOfType<ScrollViewer>().First();
var offset = index*section.ActualWidth;
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => viewer.ChangeView(offset, null, null, false));
}
(GetDescendantsOfType is from WinRTXamlToolkit.Controls.Extensions)
What I'd expect is that calling
ScrollHubSection(myHub, section1, 1)
should scroll the viewer to an HorizontalOffset of 360, while
ScrollHubSection(myHub, section2, 2)
(with offset becomes correctly 720) still leaves HorizontalOffset to 360 which is quite nonsens.
If I manually insert the offset by doing
viewer.ChangeView(1000, null, null, false));
it goes to an HorizontalOffset of 1000, but if I do
viewer.ChangeView(offset, null, null, false));
when offset is 1000 it doesn't work.
This is really driving me mad because I've wasted the whole afternoon trying to fix it without success.
Can you please help me?

You can do it like this.
async private static Task ScrollHubToSection(Hub hub, HubSection section, int index)
{
var dispatcher = hub.Dispatcher;
var viewer = hub.GetDescendantsOfType<ScrollViewer>().First();
double offset = 0;
for (int i = 0; i < index; i++)
{
offset += hub.Sections[i].ActualWidth;
}
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => viewer.ChangeView(offset, null, null, false));
}

Related

Resize an already drawn rectangle paperjs

I am creating a rectangle in a canvas using paper JS. The following is code for drawing the rectangle in vue.js.
created () {
const toolDrag = event => {
let trackingRect = new paper.Path.Rectangle(event.downPoint, event.point)
trackingRect.strokeColor = new paper.Color('#2661D8')
trackingRect.strokeColor.alpha = 0.7
trackingRect.strokeWidth = this.strokeWidth
trackingRect.removeOn({
drag: true,
up: true
})
}
// Finalise rectangle properties and draw.
let $this = this;
const toolUp = event => {
let newRect = new paper.Path.Rectangle(event.downPoint, event.point)
newRect.strokeColor = new paper.Color(this.getColor().stroke)
newRect.fillColor = new paper.Color(this.getColor().fill)
newRect.strokeWidth = this.strokeWidth
newRect.selected = true;
// Custom data attribute:
newRect.data.type = 'rectangle'
newRect.data.class = ''
// Flag the annotation has been edited and the changes are not saved
this.flagAnnotationEdits()
}
this.toolRect = new paper.Tool();
this.toolRect.onMouseDrag = toolDrag;
this.toolRect.onMouseUp = toolUp;
},
Now I want to allow user to resize this drawn rectangle by dragging any corner of the rectangle, but I am kind of stuck and unable to understand how to do this.
I have seen solutions for resizing a rectangle by changing bounds, but could not find solution for my use case. Any help is appreciated.
There are many ways to achieve what you want.
One of the simplest that I found is to recreate the rectangle, each time you drag one of its corners.
This way, you only have to know the dragged corner position and the opposite corner position and you can easily create the proper rectangle.
Here is a sketch demonstrating a possible implementation.
I think that you should easily be able to transpose it to your specific case.
// This settings controls the handles size.
const HANDLES_RADIUS = 5;
// This flag allow us to know when we are dragging an handle.
let dragging = false;
// This will store the offset from the handle position to the
// mouse down point. This allow a better drag UX.
let offset;
// This will store the point representing the opposite rectangle corner from
// the one being dragged.
let oppositePoint;
// We start by creating a rectangle in the middle of the canvas.
// This rectangle will be replaced each time a corner is dragged.
let rectangle = createRectangle(view.center - 50, view.center + 50);
// Then, we create an handle for each of the corners.
// These will be used to modify the rectangle shape.
const handles = rectangle.segments.map((segment, index) => new Path.Rectangle({
from: segment.point - HANDLES_RADIUS,
to: segment.point + HANDLES_RADIUS,
fillColor: 'orange',
// We store the segment index bound to this specific handle in the custom
// data object. This will allow us to know, when an handle is clicked,
// which segment is concerned by the event.
data: { segmentIndex: index },
// On mouse down on an handle...
onMouseDown: function(event) {
// ...get and store the opposite segment point.
// We will use it later to redraw the rectangle.
const oppositeSegmentIndex = (this.data.segmentIndex + 2) % 4;
oppositePoint = rectangle.segments[oppositeSegmentIndex].point;
// Store the offset.
offset = event.point - rectangle.segments[this.data.segmentIndex].point;
// Activate dragging state.
dragging = true;
}
}));
// On mouse move...
function onMouseMove(event) {
// ...don't do nothing if we are not dragging an handle.
if (!dragging) {
return;
}
// Get the new corner position by applying the offset to the event point.
const activePoint = event.point + offset;
// Recreate the rectangle with the new corner.
rectangle.remove();
rectangle = createRectangle(oppositePoint, activePoint);
// For each corner...
rectangle.segments.forEach((segment, index) => {
// ...place an handle...
handles[index].position = segment.point;
// ...store the potentially new segment <=> corner bound.
handles[index].data.segmentIndex = index;
});
}
// On mouse up...
function onMouseUp() {
// Disable dragging state
dragging = false;
}
//
// HELPERS
//
// This method is used to avoid duplicating the rectangle instantiation code.
function createRectangle(from, to) {
return new Path.Rectangle({
from,
to,
strokeColor: 'orange'
});
}

How to read only the CalendarView control in UWP

On my UWP app, I have added a CalendarView and programmatically selected several dates. Now I want to make it read only to the end user. But the control does not provide such functionality and making IsEnabled = False is not an option.
Please let me know a way of making the control read only.
Thanks.
NOTE: I get this problem with the keyboard. When a user hits enter key or space bar key, Calender changes its view. I want to make it static.
Setting IsEnabled to False is the right way to go. I guess the reason that you don't want to use it is because doing so would dim the whole control. So the real question comes down to - how to disable the control while making its appearance unchanged?
Generally, the appearance of a disabled (i.e. IsEnabled="False") control is defined in the Disabled state inside its style. In your case though things will get a little bit more complicated because you need to update more than one styles as the CalendarView is a complex control.
Here is how it can be done.
First, you need to find the default style of the CalendarView control and put it inside your App.xaml. Then, search for <VisualState x:Name="Disabled"> and comment out everything within this visual state. There should be two of them 'cause the first one is for the up/down button and second one is for all the day of week text.
Apart from this, you also need to manually update the color of each CalendarViewDayItem simply because the TextBlock which is used to display the day number doesn't live inside its style.
To do this in code, go to my answer here and copy overChildren extension method, then you just need to run the following code in your Loaded event. Note you might want to give it a little bit delay so the Disabled state is applied properly.
Loaded += async (s, e) =>
{
await Task.Delay(100);
var dayItems = MyCalendarView.Children().OfType<CalendarViewDayItem>();
foreach (var dayItem in dayItems)
{
var textBlock = dayItem.Children().OfType<TextBlock>().Single();
textBlock.Foreground = new SolidColorBrush(Colors.Black);
}
};
After doing all this, your CalendarView control will now appear just as normal when it's actually disabled.
Hope this helps!
You could also try
YourCalendarView.IsHitTestVisible = false;
or
<CalendarView IsHitTestVisible="False" />
For anyone who's looking for a complete code here it is. All this is thanks to Justin XL.
static int minOne = -1;
static int plsOne = 1;
public static async Task ManipCalenderUserInterface(List<CalendarView> calenders, List<List<DateTime>> datesListByMonth)
{
try
{
CoreDispatcher coreDispatcher = Window.Current.Dispatcher;
await Task.Run(async () =>
{
await Task.Delay(1000);
//coreDispatcher is required since you're not on the main thread and you can't manip UI from worker threads
await coreDispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
int cnt = 0;
bool cont = false;
bool stop = false;
//Highlight color. I get it from my resource file
SolidColorBrush colorBrush = (SolidColorBrush)Application.Current.Resources["FgBlue1"];
//This code wrote to manipulate multiple calenders
foreach (var item in calenders)
{
var dayItems = item.Children().OfType<CalendarViewDayItem>();
var firstDayOfMonth = new DateTime(dayItems.Skip(15).First().Date.Year, dayItems.Skip(15).First().Date.Month, plsOne);
var lastDayOfMonth = firstDayOfMonth.AddMonths(plsOne).AddDays(minOne);
foreach (var dayItem in dayItems)
{
var textBlock = dayItem.Children().OfType<TextBlock>().Single();
int dayNum = System.Convert.ToInt32(textBlock.Text);
if (dayNum == 1 && !stop) cont = true;
if (cont && dayNum >= 1 && dayNum <= lastDayOfMonth.Day)
{
textBlock.Foreground = new SolidColorBrush(Colors.Black);
//Bold the today date on this month's calender
if (dayItem.Date == DateTime.Now.Date)
{
textBlock.FontWeight = FontWeights.Bold;
}
//datesListByMonth: contains all the dates need to be highlighted on th calender
if (datesListByMonth[cnt] != null && datesListByMonth[cnt].Contains(dayItem.Date.Date))
dayItem.Background = colorBrush;
}
if (cont && dayNum == lastDayOfMonth.Day)
{
cont = false;
stop = true;
}
}
cnt++;
cont = false;
stop = false;
}
});
});
}
catch (Exception ex)
{
}
}
I know this is late, but assuming that you want the user to still be able to select and scroll through months, etc. this is all you should need to do:
<CalendarView SelectionMode="Multiple">
<CalendarView.Resources>
<Style TargetType="CalendarViewDayItem">
<Setter Property="IsHitTestVisible"
Value="False" />
<Setter Property="IsTabStop"
Value="False" />
</Style>
</CalendarView.Resources>
</CalendarView>
The IsHitTestVisible setter will ensure that the DayViewItem cannot be clicked or tapped, and the IsTabStop setter prevents keyboard focus on it.
This allows you to keep the full CalendarView functionality while simply disabling user date-selection.

A slider to separate 2 content divs

I want to create an "image comparison slider" for contents. Example:
Codepen Link
// Call & init
$(document).ready(function(){
$('.ba-slider').each(function(){
var cur = $(this);
// Adjust the slider
var width = cur.width()+'px';
cur.find('.resize img').css('width', width);
// Bind dragging events
drags(cur.find('.handle'), cur.find('.resize'), cur);
});
});
// Update sliders on resize.
// Because we all do this: i.imgur.com/YkbaV.gif
$(window).resize(function(){
$('.ba-slider').each(function(){
var cur = $(this);
var width = cur.width()+'px';
cur.find('.resize img').css('width', width);
});
});
function drags(dragElement, resizeElement, container) {
// Initialize the dragging event on mousedown.
dragElement.on('mousedown touchstart', function(e) {
dragElement.addClass('draggable');
resizeElement.addClass('resizable');
// Check if it's a mouse or touch event and pass along the correct value
var startX = (e.pageX) ? e.pageX : e.originalEvent.touches[0].pageX;
// Get the initial position
var dragWidth = dragElement.outerWidth(),
posX = dragElement.offset().left + dragWidth - startX,
containerOffset = container.offset().left,
containerWidth = container.outerWidth();
// Set limits
minLeft = containerOffset + 10;
maxLeft = containerOffset + containerWidth - dragWidth - 10;
// Calculate the dragging distance on mousemove.
dragElement.parents().on("mousemove touchmove", function(e) {
// Check if it's a mouse or touch event and pass along the correct value
var moveX = (e.pageX) ? e.pageX : e.originalEvent.touches[0].pageX;
leftValue = moveX + posX - dragWidth;
// Prevent going off limits
if ( leftValue < minLeft) {
leftValue = minLeft;
} else if (leftValue > maxLeft) {
leftValue = maxLeft;
}
// Translate the handle's left value to masked divs width.
widthValue = (leftValue + dragWidth/2 - containerOffset)*100/containerWidth+'%';
// Set the new values for the slider and the handle.
// Bind mouseup events to stop dragging.
$('.draggable').css('left', widthValue).on('mouseup touchend touchcancel', function () {
$(this).removeClass('draggable');
resizeElement.removeClass('resizable');
});
$('.resizable').css('width', widthValue);
}).on('mouseup touchend touchcancel', function(){
dragElement.removeClass('draggable');
resizeElement.removeClass('resizable');
});
e.preventDefault();
}).on('mouseup touchend touchcancel', function(e){
dragElement.removeClass('draggable');
resizeElement.removeClass('resizable');
});
}
Same as above, but instead of images, I want to create content divs with text, images, html. Like 2 pages with a slider to compare both.
Your comparison script utilizes a resizing a div, so there's no way (that I know of) to achieve this without some considerable trickery (or how to achieve the same effect without resizing).
The only way to prevent the text from automatically wrapping is by using ...
white-space: nowrap;
... but that would just result in one continuous line of text.
Breaks <br> could be inserted from there, but that would just look awful and negate any resizing.
Personally, my approach would be to render the text within a canvas element first (https://www.w3schools.com/graphics/canvas_text.asp).
Since a canvas is treated nearly identical to that of an image, it wouldn't require any changes to the existing comparison script.

EaselJs shape hitTest unable to set alpha or visiblity

From the sample I created
I am unable to set the circle's alpha / visiblity when mousedown draw line. But I am able to console log it when its detect hitTest.. Is there advise in this?
Below is the block of codes:
var canvas, stage;
var drawingCanvas;
var oldPt;
var oldMidPt;
var title;
var color;
var stroke;
var colors;
var index;
var rect, circle1;
var currMidPt;
$(document).ready(function() {
init();
});
function init() {
canvas = document.getElementById("canvas");
index = 0;
colors = ["#828b20", "#b0ac31", "#cbc53d", "#fad779", "#f9e4ad", "#faf2db", "#563512", "#9b4a0b", "#d36600", "#fe8a00", "#f9a71f"];
//check to see if we are running in a browser with touch support
stage = new createjs.Stage(canvas);
stage.autoClear = false;
stage.enableDOMEvents(true);
createjs.Touch.enable(stage);
createjs.Ticker.setFPS(24);
createjs.Ticker.on("tick", tick);
drawingCanvas = new createjs.Shape();
stage.addEventListener("stagemousedown", handleMouseDown);
stage.addEventListener("stagemouseup", handleMouseUp);
title = new createjs.Text("Click and Drag to draw", "36px Arial", "#777777");
title.x = 300;
title.y = 200;
rect = new createjs.Shape();
rect.graphics.beginFill("#000").drawRect(0, 0, stage.canvas.width, stage.canvas.height);
var container = new createjs.Container();
container.x = 0;
container.y = 0;
stage.addChild(container, title);
stage.addChild(drawingCanvas);
circle1 = new createjs.Shape();
circle1.graphics.beginFill("#990000").drawCircle(120,120,40);
container.addChild(circle1);
stage.update();
}
function handleMouseDown(event) {
if (!event.primary) { return; }
if (stage.contains(title)) {
stage.clear();
stage.removeChild(title);
}
color = colors[(index++) % colors.length];
stroke = Math.random() * 30 + 10 | 0;
oldPt = new createjs.Point(stage.mouseX, stage.mouseY);
oldMidPt = oldPt.clone();
stage.addEventListener("stagemousemove", handleMouseMove);
}
function handleMouseMove(event) {
if (!event.primary) { return; }
var midPt = new createjs.Point(oldPt.x + stage.mouseX >> 1, oldPt.y + stage.mouseY >> 1);
drawingCanvas.graphics.clear().setStrokeStyle(stroke, 'round', 'round').beginStroke(color).moveTo(midPt.x, midPt.y).curveTo(oldPt.x, oldPt.y, oldMidPt.x, oldMidPt.y);
oldPt.x = stage.mouseX;
oldPt.y = stage.mouseY;
oldMidPt.x = midPt.x;
oldMidPt.y = midPt.y;
currMidPt = midPt;
if(circle1.hitTest(currMidPt.x, currMidPt.y)) {
console.log('test');
circle1.alpha = 0.6;
circle1.visible = false;
}
stage.update();
}
function tick(event) {
// console.log(ndgmr.checkPixelCollision(drawingCanvas,circle1,0,false));
stage.update(event);
}
function handleMouseUp(event) {
if (!event.primary) { return; }
stage.removeEventListener("stagemousemove", handleMouseMove);
}
The main reason this doesn't work is because you are using a stage that never clears itself. This gives you the benefit of a "paint brush", since it just adds new curves as you draw, but the circle can never be removed, since it is painted on to the canvas. If you change the alpha, it just draws on top of the current circle. This is also why your circle gets all aliased, as it constantly draws on top of itself, multiplying the alpha.
Here is a quick edit that shows the circle moving its x position instead of adjusting the alpha. Any time you roll over the original position it will move 10 pixels to the right.
http://codepen.io/lannymcnie/pen/redrgo?editors=0010
The hitTest code you have used is not correct though, since it always checks the x/y position of the pen against the local coordinates of the circle -- this means the position of the circle doesn't matter. To fix this, you need to find the local coordinates instead:
currMidPt = circle1.globalToLocal(midPt.x, midPt.y);
Here is an updated fiddle showing that behaviour: http://codepen.io/lannymcnie/pen/grejVg?editors=0010
However, to get the effect you are probably looking for, you have to take a different approach. Instead of using the stage auto-clear, use a cached Shape, and update the cache when you draw. This will provide the paint effect, but let the rest of the stage get updated as usual.
// Remove this!
stage.autoClear = false;
// Cache the drawingCanvas once after creating it
drawingCanvas = new createjs.Shape();
drawingCanvas.cache(0,0,canvas.width,canvas.height);
// After drawing, update the cache. This is at the end of the mousemove function.
// Use "source-over" to apply the new contents on top, instead of clearing the cache.
drawingCanvas.updateCache("source-over");
stage.update();
I also made a few other changes:
Removed the Ticker listener that updates the stage. Your mousemove already updates the stage when the contents change. If you want to reintroduce the ticker update for other content, then remove the stage.update() calls everywhere else, as they are redundant.
Moved the drawingCanvas below the circle/container. This just makes sure the circle is always visible for the demo
Here is a final demo with these changes:
http://codepen.io/lannymcnie/pen/NNYLPX?editors=0010
Hope that helps!

Panning the map to certain extent javascript API

I want to limit map extent to the initial extent of the map and limit user from panning more than certain extent.
I tried following but nothing has changed:
map = new Map( "map" , {
basemap: "gray",
center: [-85.416, 49.000],
zoom : 6,
logo: false,
sliderStyle: "small"
});
dojo.connect(map, "onExtentChange", function (){
var initExtent = map.extent;
var extent = map.extent.getCenter();
if(initExtent.contains(extent)){}
else{map.setExtent(initExtent)}
});
Just to flesh out Simon's answer somewhat, and give an example. Ideally you need two variables at the same scope as map:
initExtent to store the boundary of your valid extent, and
validExtent to store the last valid extent found while panning, so that you can bounce back to it.
I've used the newer dojo.on event syntax as well for this example, it's probably a good idea to move to this as per the documentation's recommendation - I assume ESRI will discontinue the older style at some point.
var map;
var validExtent;
var initExtent;
[...]
require(['dojo/on'], function(on) {
on(map, 'pan', function(evt) {
if ( !initExtent.contains(evt.extent) ) {
console.log('Outside bounds!');
} else {
console.log('Updated extent');
validExtent = evt.extent;
}
});
on(map, 'pan-end', function(evt) {
if ( !initExtent.contains(evt.extent) ) {
map.setExtent(validExtent);
}
});
});
You can do the same with the zoom events, or use extent-change if you want to trap everything. Up to you.
It looks like your extent changed function is setting the initial extent variable to the maps current extent and then checking if that extent contains the current extents centre point - which of course it always will.
Instead, declare initExtent at the same scope of the map variable. Then, change the on load event to set this global scope variable rather than a local variable. In the extent changed function, don't update the value of initExtent, simply check the initExtent contains the entire of the current extent.
Alternatively you could compare each bound of the current extent to each bound of the initExtent, e.g. is initExtent.xmin < map.extent.xmin and if any are, create a new extent setting any exceeded bounds to the initExtent values.
The only problem is these techniques will allow the initExtent to be exceeded briefly, but will then snap the extent back once the extent changed function fires and catches up.
I originally posted this solution on gis.stackexchange in answer to this question: https://gis.stackexchange.com/a/199366
Here's a code sample from that post:
//This function limits the extent of the map to prevent users from scrolling
//far away from the initial extent.
function limitMapExtent(map) {
var initialExtent = map.extent;
map.on('extent-change', function(event) {
//If the map has moved to the point where it's center is
//outside the initial boundaries, then move it back to the
//edge where it moved out
var currentCenter = map.extent.getCenter();
if (!initialExtent.contains(currentCenter) &&
event.delta.x !== 0 && event.delta.y !== 0) {
var newCenter = map.extent.getCenter();
//check each side of the initial extent and if the
//current center is outside that extent,
//set the new center to be on the edge that it went out on
if (currentCenter.x < initialExtent.xmin) {
newCenter.x = initialExtent.xmin;
}
if (currentCenter.x > initialExtent.xmax) {
newCenter.x = initialExtent.xmax;
}
if (currentCenter.y < initialExtent.ymin) {
newCenter.y = initialExtent.ymin;
}
if (currentCenter.y > initialExtent.ymax) {
newCenter.y = initialExtent.ymax;
}
map.centerAt(newCenter);
}
});
}
And here's a working jsFiddle example: http://jsfiddle.net/sirhcybe/aL1p24xy/