My question is about using bing maps with windows phone 7. Here is a summary of what I need to do
poll a service every x seconds to retrieve a set of coordinates
if this is the first time the service is polled
plot these coordinates as custom pushpins on the map (I am using Image and MapLayer)
PinObject pin = new PinObject() //Custom object
{
Id = groupMember.Id,
PushPin = new Image()
{
Source = new System.Windows.Media.Imaging.BitmapImage(new Uri("blackpin.png", UriKind.Relative)),
Opacity = 0.8,
Stretch = System.Windows.Media.Stretch.None
},
PinLocation = new GeoCoordinate(groupMember.Latitude, groupMember.Longitude)
};
imageLayer.AddChild(pin.PushPin, pin.PinLocation); //Initialized in constructor
pinObjects.Add(pin);// Add pin object to a list to provide a handle to the objects
auto set the map zoomlevel so that all the plotted points are visible (I would assume using a LocationRect.CreateLocationRect should do)
var coords = pinObjects.Select(p => p.PinLocation).ToList();
myMap.SetView(LocationRect.CreateLocationRect(coords));
else based on the new coordinates obtained, update the locations of each of the pushpins on the map
PinObject pObj = pinObjects.FirstOrDefault(p => p.Id == groupMember.Id);
MapLayer.SetPosition(pObj.PushPin, new GeoCoordinate(groupMember.Latitude, groupMember.Longitude));
The pins load fiine and the call to the service to get their new locations loads fine as well, the problem is that their location on the map is never updated so basically they just sit still even though all this work is going on in the background, I have debugged so I know it works. How do I reset the location of the pins, if using an Image won't do, can I use a Pushpin object? How would this work?
Thanks in advance.
I've found that the best way to ensure the pushpins get updated is to call call SetView() on the map again. You can pass in the existing view to basically force a refresh. Eg; MyMapControl.SetView(MyMapControl.BoundingRectangle);
Here is an option similar to Dispatcher.BeginInvoke but it works better for me in some cases. When I really need to get off the current thread with some work I will use a private static class UICallbackTimer to offset execution just a slight amount. (typos and untested code, just pulling out pieces here you'll have to debug in your code)
UICallbackTimer is not my code but it's available on the Internet. You can get information on this class by searching "private static class UICallbackTimer"
Here is the code to execute it.
UICallbackTimer.DelayExecution(TimeSpan.FromSeconds(.01),
() => (
MapLayer.SetPosition(pObj.PushPin, new GeoCoordinate(groupMember.Latitude, groupMember.Longitude))
);
and here is the class ( I place it inside the current object, so that it remains private to my class) Add using statement for System.Threading
private static class UICallbackTimer
{
private static bool _running = false;
private static int runawayCounter = 0;
public static bool running()
{
if (_running && runawayCounter++ < 10)
return _running;
runawayCounter = 0;
_running = false;
return _running;
}
public static void DelayExecution(TimeSpan delay, Action action)
{
_running = true;
System.Threading.Timer timer = null;
SynchronizationContext context = SynchronizationContext.Current;
timer = new System.Threading.Timer(
(ignore) =>
{
timer.Dispose();
_running = false;
context.Post(ignore2 => action(), null);
}, null, delay, TimeSpan.FromMilliseconds(-1));
}
}
Great Question!
Here is some realllly ugly code, but at least its working and something to start from.
I got the main structure from here. I'd appreciate it if someone could post an answer with proper binding and less code behind.
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--RESOURCES-->
<Grid.Resources>
<DataTemplate x:Key="LogoTemplate">
<maps:Pushpin Location="{Binding PinLocation}" />
</DataTemplate>
<maps:MapItemsControl x:Name="GroupAPins"
ItemTemplate="{StaticResource LogoTemplate}"
ItemsSource="{Binding PinsA}">
</maps:MapItemsControl>
</Grid.Resources>
<Grid x:Name="ContentPanel" Grid.Row="0" Margin="12,0,12,0"/>
</Grid>
public partial class MapPage : PhoneApplicationPage
{
private ObservableCollection<PinData> _pinsA = new ObservableCollection<PinData>();
private Map MyMap;
public ObservableCollection<PinData> PinsA { get { return this._pinsA; } }
public MapPage()
{
InitializeComponent();
this.DataContext = this;
//Create a map.
MyMap = new Map();
MyMap.CredentialsProvider = new ApplicationIdCredentialsProvider("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
//Remove the List of items from the resource and add it to the map
this.LayoutRoot.Resources.Remove("GroupAPins");
MyMap.Children.Add(GroupAPins);
MyMap.Center = new GeoCoordinate(40.74569634433956, -73.96717071533204);
MyMap.ZoomLevel = 5;
//Add the map to the content panel.
ContentPanel.Children.Add(MyMap);
loadAPins_fromString();
}
//ADD PIN TO COLLECTION
private void addPin(String lat, String lon)
{
PinData tmpPin;
tmpPin = new PinData()
{
PinLocation = new GeoCoordinate(System.Convert.ToDouble(lat), System.Convert.ToDouble(lon))
};
_pinsA.Add(tmpPin);
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += delegate(object sender, EventArgs args)
{
PinsA.Remove(tmpPin);
tmpPin.PinLocation.Latitude += 1;
PinsA.Add(tmpPin);
};
timer.Start();
}
//LOAD PINS ONE BY ONE
private string loadAPins_fromString()
{
//BAD
addPin("42.35960626034072", "-71.09212160110473");
//addPin("51.388066116760086", "30.098590850830067");
//addPin("48.17972265679143", "11.54910385608672");
addPin("40.28802528051879", "-76.65668606758117");
var coords = PinsA.Select(p => p.PinLocation).ToList();
MyMap.SetView(LocationRect.CreateLocationRect(coords));
return "A PINS LOADED - STRING";
}
}
public class PinData
{
public GeoCoordinate PinLocation{get;set;}
}
Related
I am developing an application with ArcGIS Runtime SDK for .Net and following the MVVM Pattern, in my ViewModel I have an ObservableCollection of GraphicsOverlay that I have binded to the MapView in my View, now when I add a new GraphicsOverlay to the ObservableCollection and add Graphics in it, graphics are not reflection in the View,
I have implemented INotifyPropertyChanged and all other things are working fine with the ViewModel
public class MapViewModel : BaseViewModel
{
private Map map;
public Map Map
{
get { return this.map; }
set { this.map = value; }
}
public ObservableCollection<GraphicsOverlay> GraphicsOverlays { get; set; }
public MapViewModel()
{
GraphicsOverlays = new ObservableCollection<GraphicsOverlay>();
}
And in my Method that is called by any event
public void UpdateMarker(MapPoint point)
{
GraphicsOverlays[0].Graphics.Clear();
// Create a symbol to symbolize the point
SimpleMarkerSymbol symbol = new SimpleMarkerSymbol(SimpleMarkerSymbolStyle.X, System.Drawing.Color.Yellow, 20);
// Create the graphic
Graphic symbolGraphic = new Graphic(point, symbol);
// Add the graphic to the graphics overlay
var newGraphicsOverlay=new GraphicsOverlay();
GraphicsOverlays[0].Graphics.Add(symbolGraphic);
}
And in my View I have
<esri:MapView x:Name="MyMapView" Grid.Column="0" DataContext="
{StaticResource MapVM}" GraphicsOverlays="{Binding GraphicsOverlays}" Map="
{Binding Map}" Cursor="{Binding MapViewCursor}">
I am unable to find any sample that does exactly this, so how to do this, I am new to arcGIS, Thanks in advance.
Update
I have updated the UpdateMarker Method like this
public void UpdateMarker(MapPoint point)
{
GraphicsOverlays[0].Graphics.Clear();
// Create a symbol to symbolize the point
SimpleMarkerSymbol symbol = new SimpleMarkerSymbol(SimpleMarkerSymbolStyle.X, System.Drawing.Color.Yellow, 20);
// Create the graphic
Graphic symbolGraphic = new Graphic(point, symbol);
// Add the graphic to the graphics overlay
var newGraphicsOverlay = new GraphicsOverlay();
newGraphicsOverlay.Graphics.Add(symbolGraphic);
GraphicsOverlays[0] = newGraphicsOverlay;
}
But still the symbol is not showing on the map.
I think you have to use a GraphicsOverlayCollection instead of an ObservableCollection as MapView.GraphicOverlays is a GraphicsOverlayCollection and not an ObservableCollection. Something like
public GraphicsOverlayCollection<GraphicsOverlay> GraphicsOverlays { get; set; }
I have a window, and the window contains it's content and a "Loading" overlay as follows
<Grid>
<Grid>
<!-- STUFF -->
</Grid>
<Rectangle Fill="White" Opacity="0.7" Visibility="{Binding VisibilityWait, Mode=TwoWay,
Converter={StaticResource BoolToVisibilityConverter}}" />
</Grid>
My ViewModel then implements INotifyPropertyChanged and includes the property;
private bool visibilityWait;
public bool VisibilityWait
{
get
{
return visibilityWait;
}
set
{
visibilityWait = value;
OnPropertyChanged("VisibilityWait");
}
}
I know that this is set up correctly because if I set the VisibilityWait to true in the constructor, the window displays with the "Loading" overlay as expected and vice versa... However, if i try to do this during a method e.g.;
private void someMethod()
{
VisibilityWait = true;
//... Do things
VisibilityWait = false;
}
Then during the time that the program is in the "do things" section, the UI does not update to show the loading overlay.
How do I solve this issue?
EDIT: I found my own solution to this problem. See the answer below.
Usually when a function is taking place, any updates to the UI are blocked until the end of the function. This is because the frame does not get pushed until the end of the function. You can force this update by calling a method like this;
void AllowUIToUpdate()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Render, new DispatcherOperationCallback(delegate (object parameter)
{
frame.Continue = false;
return null;
}), null);
Dispatcher.PushFrame(frame);
//EDIT:
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
}
EDIT: I added in an extra line to the AllowUIToUpdate function, and now everything functions as expected!
An improvement of #Lewis_Heslop, this seams to work perfectly with MVVM:
private static void AllowUIToUpdate()
{
DispatcherFrame frame = new();
// DispatcherPriority set to Input, the highest priority
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Input, new DispatcherOperationCallback(delegate (object parameter)
{
frame.Continue = false;
Thread.Sleep(20); // Stop all processes to make sure the UI update is perform
return null;
}), null);
Dispatcher.PushFrame(frame);
// DispatcherPriority set to Input, the highest priority
Application.Current.Dispatcher.Invoke(DispatcherPriority.Input, new Action(delegate { }));
}
I have generated a graph using the tutorial provided by James McCaffrey : http://msdn.microsoft.com/en-us/magazine/ff714591.aspx
Iam able to do so successfully. Also I have added a tooltip as follows:
plotter.AddLineGraph(compositeDataSource1,
new Pen(Brushes.Blue, 2),
new CircleElementPointMarker{ Size = 10.0, Fill = Brushes.Red ,Tooltip="Coordinates"},
new PenDescription("Number bugs open"));
My Question is : how do I display the co-ordinates of the point in tooltip.?
This is how I solved my issue.
EnumerableDataSource<TPoint> edc;
edc= new EnumerableDataSource<TPoint>(List_Of_TPoint);
edc.SetXMapping(x => dateAxis.ConvertToDouble(x.X));
edc.SetYMapping(y => Convert.ToDouble(y.Y));
edc.AddMapping(CircleElementPointMarker.ToolTipTextProperty, s => String.Format("Y-Data : {0}\nX-Data : {1}", s.Y, s.X));
I just added above mapping while creating my EnumerableDataSource.
And then added the edc to plotter.
plotter.AddLineGraph(
edc,
new Pen(Brushes.Transparent, 3),
new CircleElementPointMarker
{
Size = 15,
Brush = border,
Fill = c2
},
null
);
Using the ElementMarkerPointsGraph and CircleElementPointMarker could be very resource expensive. For each Point an Ellipse will be created. Also the Mapping (building the string for Tooltip) will be executed for every point, even if nobody wants to see the tooltip.
So my way is to use a MarkerPointsGraph and set it's Tooltip dynamically.
Xaml:
<d3:MarkerPointsGraph Name="MyMarkerPointsGraph" DataSource="{Binding Values}" ToolTip="Dummy" ToolTipOpening="CircleMarker_OnToolTipOpening">
<d3:MarkerPointsGraph.Marker>
<d3:CirclePointMarker />
</d3:MarkerPointsGraph.Marker>
</d3:MarkerPointsGraph>
and here the Code behind:
private void CircleMarker_OnToolTipOpening(object sender, ToolTipEventArgs e)
{
var pos = Mouse.GetPosition(MyMarkerPointsGraph);
var transform = GetTransform(MyMarkerPointsGraph);
transform.ScreenToData(pos);
var dataPoint = transform.ScreenToData(pos);
MyMarkerPointsGraph.ToolTip = $"Y-Data : {dataPoint.Y}\nX-Data : {dataPoint.X}";
}
public static CoordinateTransform GetTransform(PointsGraphBase graph)
{
if (!(graph.Plotter is Plotter2D))
return null;
var transform = ((Plotter2D)graph.Plotter).Viewport.Transform;
if (graph.DataTransform != null)
transform = transform.WithDataTransform(graph.DataTransform);
return transform;
}
I have been reading Mark Seeman's book on dependency injection in .NET and I'm struggling to configure composition root in WPF application.
My container will be registered in the application startup method:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var container = new Container();
container.Configure(r =>
{
r.For<IAccountServices>().Use<AccountServicesProxy>();
r.For<MainWindow>().Use<MainWindow>();
});
}
This makes sense as the application startup represents my composition root.
WPF windows in my application are based on view models. View models use constructor injection. E.g. I may compose a view model by injecting implementation of IAccountServices.
When it comes to creating my main window, I can do the following inside of the OnStartup method:
var mainWindow = container.GetInstance<MainWindow>();
mainWindow.Show();
Once I'm inside of the main window, I might want open up another window. So far I've been able to come up with one way of doing this, which is to create a window factory and ask window factory to resolve instance of the window. I'll have to make sure that window factory is available in every view model that might need to open a new window. In my mind this is as bad as passing IoC container around my application (service locator anti-pattern comes to mind).
Does this approach seem right to you? My gut feeling tells me that this is wrong, but I haven't come up with a better way of achieving this (yet).
I think before implement patterns of behavior, such as a Mediator, and the like, need to decide on a generic pattern for easy application structure. For this purpose, namely, for the create independent windows, well suited Abstract factory pattern.
Creation of the windows can be implemented on the side ViewModel using methods such as IDialogService. But I think that this task should be implemented on the side View, because the Window object refers to the View and not to ViewModel. So, you must create MVVM style architecture that it allows create independent windows using design patterns.
I created a project in which an Abstract factory creates a Window on the side of the View using the attached behavior. Abstract factory also implements the Singleton pattern to create a global point of access and to ensure the uniqueness of the newly constructed object. Attached behavior implicitly implements pattern Decorator who is a wrapper for an abstract factory that is used on the side of XAML. To an Abstract factory does not refer to objects which are located in ViewModel is used a Proxy pattern which is a ContentControl with DataTemplate without DataType. Also used Command pattern for independent action between objects. As a result, this project uses the following patterns:
Abstract factory
Singleton
Decorator
Proxy
Command
The project structure looks like this:
In the attached behavior has attached dependency property Name, which is transmitted in the name of the new window. For him registered PropertyChangedEvent, which is a call Make method an abstract factory:
private static void IsFactoryStart(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var window = sender as Window;
if (window == null)
{
return;
}
if (e.NewValue is String && String.IsNullOrEmpty((string)e.NewValue) == false)
{
_typeWindow = (string)e.NewValue;
if (_typeWindow != null)
{
var newWindow = WindowFactory.Instance.Make(_typeWindow);
newWindow.Show();
}
}
}
WindowFactory together with the Singleton pattern looks like this:
public class WindowFactory : IWindowFactory
{
#region WindowFactory Singleton Instance
private static WindowFactory _instance = null;
private static readonly object padlock = new object();
public static WindowFactory Instance
{
get
{
lock (padlock)
{
if (_instance == null)
{
_instance = new WindowFactory();
}
return _instance;
}
}
}
#endregion
public Window Make(string TypeWindow)
{
if (TypeWindow.Equals("WindowOneViewProxy"))
{
var windowOne = new Window();
windowOne.Width = 450;
windowOne.Height = 250;
windowOne.WindowStartupLocation = WindowStartupLocation.CenterScreen;
windowOne.Title = TypeWindow;
windowOne.ContentTemplate = Application.Current.Resources[TypeWindow] as DataTemplate;
return windowOne;
}
else if (TypeWindow.Equals("WindowTwoViewProxy"))
{
var windowTwo = new Window();
windowTwo.Width = 500;
windowTwo.Height = 200;
windowTwo.WindowStartupLocation = WindowStartupLocation.CenterScreen;
windowTwo.Title = TypeWindow;
windowTwo.ContentTemplate = Application.Current.Resources[TypeWindow] as DataTemplate;
return windowTwo;
}
else if (TypeWindow.Equals("WindowThreeViewProxy"))
{
var windowThree = new Window();
windowThree.Width = 400;
windowThree.Height = 140;
windowThree.WindowStartupLocation = WindowStartupLocation.CenterScreen;
windowThree.Title = TypeWindow;
windowThree.ContentTemplate = Application.Current.Resources[TypeWindow] as DataTemplate;
return windowThree;
}
else
throw new Exception("Factory can not create a: {0}" + TypeWindow);
}
}
For the property Window.ContentTemplate set DataTemplate from resources. ContentTemplate is responsible for the visual representation, in order to bind properties from ViewModel, you need to set the object to Content. But in this case, the Abstract factory reference will to ViewModel, and to avoid them and using the proxy pattern as follows:
WindowOneProxyView
<DataTemplate x:Key="WindowOneViewProxy">
<ContentControl ContentTemplate="{StaticResource WindowOneViewRealObject}">
<ViewModels:WindowOneViewModel />
</ContentControl>
</DataTemplate>
WindowOneViewRealObject
<DataTemplate x:Key="WindowOneViewRealObject" DataType="{x:Type ViewModels:WindowOneViewModel}">
<Grid>
<Label Content="{Binding Path=WindowOneModel.TextContent}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Background="Beige" />
<Button Content="One command"
Width="100"
Height="30"
HorizontalAlignment="Center"
Command="{Binding OneCommand}" />
</Grid>
</DataTemplate>
In DataTemplate proxy is not specified DataType, but it is in the real object.
In MainViewModel has commands to simply set the window name, which will give input for attached behavior:
MainModel
public class MainModel : NotificationObject
{
#region TypeName
private string _typeName = null;
public string TypeName
{
get
{
return _typeName;
}
set
{
_typeName = value;
NotifyPropertyChanged("TypeName");
}
}
#endregion
}
MainViewModel
public class MainViewModel
{
#region MainModel
private MainModel _mainModel = null;
public MainModel MainModel
{
get
{
return _mainModel;
}
set
{
_mainModel = value;
}
}
#endregion
#region ShowWindowOneCommand
private ICommand _showWindowOneCommand = null;
public ICommand ShowWindowOneCommand
{
get
{
if (_showWindowOneCommand == null)
{
_showWindowOneCommand = new RelayCommand(param => this.ShowWindowOne(), null);
}
return _showWindowOneCommand;
}
}
private void ShowWindowOne()
{
MainModel.TypeName = "WindowOneViewProxy";
}
#endregion
#region ShowWindowTwoCommand
private ICommand _showWindowTwoCommand = null;
public ICommand ShowWindowTwoCommand
{
get
{
if (_showWindowTwoCommand == null)
{
_showWindowTwoCommand = new RelayCommand(param => this.ShowWindowTwo(), null);
}
return _showWindowTwoCommand;
}
}
private void ShowWindowTwo()
{
MainModel.TypeName = "WindowTwoViewProxy";
}
#endregion
#region ShowWindowThreeCommand
private ICommand _showWindowThreeCommand = null;
public ICommand ShowWindowThreeCommand
{
get
{
if (_showWindowThreeCommand == null)
{
_showWindowThreeCommand = new RelayCommand(param => this.ShowWindowThree(), null);
}
return _showWindowThreeCommand;
}
}
private void ShowWindowThree()
{
MainModel.TypeName = "WindowThreeViewProxy";
}
#endregion
public MainViewModel()
{
MainModel = new MainModel();
}
}
MainWindow looks as:
<Window x:Class="WindowFactoryNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WindowFactoryNamespace.ViewModels"
xmlns:AttachedBehaviors="clr-namespace:WindowFactoryNamespace.AttachedBehaviors"
AttachedBehaviors:WindowFactoryBehavior.Name="{Binding Path=MainModel.TypeName}"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="300" Width="300">
<Window.DataContext>
<this:MainViewModel />
</Window.DataContext>
<WrapPanel>
<Button Content="WindowOne"
Margin="10"
Command="{Binding ShowWindowOneCommand}" />
<Button Content="WindowTwo"
Margin="10"
Command="{Binding ShowWindowTwoCommand}" />
<Button Content="WindowThree"
Margin="10"
Command="{Binding ShowWindowThreeCommand}" />
</WrapPanel>
</Window>
Test View-ViewModel for the first window looks like this (they practically identical):
WindowOneModel
public class WindowOneModel : NotificationObject
{
#region TextContent
private string _textContent = "Text content for WindowOneView";
public string TextContent
{
get
{
return _textContent;
}
set
{
_textContent = value;
NotifyPropertyChanged("TextContent");
}
}
#endregion
}
WindowOneViewModel
public class WindowOneViewModel
{
#region WindowOneModel
private WindowOneModel _windowOneModel = null;
public WindowOneModel WindowOneModel
{
get
{
return _windowOneModel;
}
set
{
_windowOneModel = value;
}
}
#endregion
#region OneCommand
private ICommand _oneCommand = null;
public ICommand OneCommand
{
get
{
if (_oneCommand == null)
{
_oneCommand = new RelayCommand(param => this.One(), null);
}
return _oneCommand;
}
}
private void One()
{
WindowOneModel.TextContent = "Command One change TextContent";
}
#endregion
public WindowOneViewModel()
{
WindowOneModel = new WindowOneModel();
}
}
This project is available at this link.
Output
MainWindow
WindowOne
WindowTwo
WindowThree
IMHO, there is no need to over complicate the solution for the sake of MVVM purity. You risk the subsequent developers not understanding your elegant solution and break it. In fact there is a good chance of that as "pure" implementations tend to be not that readable because of the complexity.
IMHO, any solution where a problem is permanently solved under an abstraction with minimal code overhead and simplicity in its usage is better than doing considerable overhead every time the solution is used even if "purity" is achieved(it won't serve any purpose). The problem of showing dialog in the application has to be solved once and it should be easy to use it in the future.
Composing view models is perfectly fine, and could make life easier by allowing view models to interact without drama
A dialog service can be created which will act as a wrapper for all your dialog needs in the application. You can inject the Dialog Service and the child view models which needs to be displayed in a window, to your parent view model. When you need to display the window, ask the Dialog service to do it, passing it the view model instance and view name.
Note:code is not complied or tested
public class DialogService : IDialogService
{
IEventAggregator _eventAggregator;
bool _fatalError;
//Provides a wrapper function which will connect your view and view model and open a
//dialog
public Window ShowCustomDialog<TViewModel>(string name, TViewModel viewModel, bool
modal, double left, double top, Action<bool?> OnClose, int width, int height)
{
if (_fatalError == true)
{
return null;
}
Window view = new Window(name);
if (viewModel != null)
{
view.DataContext = viewModel;
}
if (left != -1.0 && top != -1.0)
{
view.WindowStartupLocation = WindowStartupLocation.Manual;
view.Left = left;
view.Top = top;
}
else
{
view.WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
if (width != -1 && height != -1)
{
view.Width = width;
view.Height = height;
}
view.Closed += (o, e) =>
{
_eventAggregator.GetEvent<NotifyDialogAction>().Publish(false);
if (OnClose != null)
{
OnClose(e.DialogResult);
}
};
view.Loaded += (o, e) =>
{
_eventAggregator.GetEvent<NotifyDialogAction>().Publish(true);
Window window = o as Window;
if (window != null)
{
double dialogWidth = window.ActualWidth;
double screenWidth =
Application.Current.RootVisual.RenderSize.Width;
double dialogLeft = window.Left;
if (dialogLeft + dialogWidth > screenWidth)
{
window.Left = screenWidth - dialogWidth;
}
double dialogHeight = window.ActualHeight;
double screenHeight =
Application.Current.RootVisual.RenderSize.Height;
double dialogTop = window.Top;
if (dialogTop + dialogHeight > screenHeight)
{
window.Top = screenHeight - dialogHeight;
}
}
};
if (modal)
{
view.ShowDialog();
}
else
{
view.Show();
}
return view;
}
//Add more functions. For example to pop up a message box etc.
}
Usage
public class ComposedVM
{
public ViewModelA objA{get;set;}
public ViewModelB objB{get;set;}
IDialogService dialogService{get;set;}
public ComposedVM(ViewModelA a, ViewModelB b, IDialogService dlg )
{
objA = a;
objB = b;
dialogService = dlg
}
public void OnShowWindowACommand()
{
dialogService .ShowCustomDialog<object>(
DialogNames.ViewA/*view name constant*/, objA, true, -1.0, -1.0,
result =>
{
if (result == true)
{
dialogService.ShowMessageDialog(ApplicationStrings.SuccessFulOperation);
}
});
}
}
An event/message based communication can be used between modules. Using it for related view models in a module is an overkill IMHO.
Pushing container instance through constructor is a bad idea in 99% of cases, because container is a service locator. The main disadvantages of this approach are:
dependency from concrete implementation of container;
unclear API of your classes, which also leads to fragile unit tests.
There are many ways to create window in MVVM fashion:
using Mediators (like IMessenger in MvvmLight, IEventAggregator in Caliburn.Micro);
using special IDialogService;
using attached behaviours;
using Action that inserted via ViewModel constructor;
using Controllers.
I need to find UIElements in (rectangle/area/bounds).
MainWindow I'm doing the following:
I register the mouse down as the start position.
I regsiter the mouse up position.
Now I need to find ll (buttons, textboxes, etc) in the rectangle between start
postion and the end position.
I found in the msdn the HitTest approach but it is only for one point. I think, walking through all points in the founded
rectangle it is a performance disaster.
http://msdn.microsoft.com/en-us/library/ms752097.aspx
My code based on MVVM pattern:
private ObservableCollection<UIElementViewModel> wells;
private Point stratPoint; // Mouse down
public ICommand MouseUpRightCommand
{
get
{
if (this.mouseUpRightCommand == null)
{
this.mouseUpRightCommand = new RelayCommands(
param =>
{
if (param is MouseButtonEventArgs)
{
var e = (param as MouseButtonEventArgs);
//Set the end point
endPosition = e.GetPosition(((ItemsControl)e.Source));
// for example, here I want to find all controls(UIElements) in the
// founded rectangle of stratPoint and endPosition.
}
});
}
return this.mouseUpRightCommand;
}
}
Any other idea or a better approach?
Thanks
I would use FrameworkElement (which extends UIElement) instead of UIElement, in order to use ActualWidth and ActualHeight properties
Then create a static class which does some math MouseUtils
with those static fields
private static double _dContainerTop;
private static double _dContainerBottom;
private static double _dContainerLeft;
private static double _dContainerRight;
private static double _dCursorTop;
private static double _dCursorLeft;
private static double _dCursorRight;
private static double _dCursorBottom;
and those static methods
private static void FindValues(FrameworkElement element, Visual rootVisual)
{
var containerTopLeft = container.TransformToAncestor(rootVisual).Transform(new Point(0, 0));
_dContainerTop = containerTopLeft.Y;
_dContainerBottom = _dContainerTop + container.ActualHeight;
_dContainerLeft = containerTopLeft.X;
_dContainerRight = _dContainerLeft + container.ActualWidth;
}
and
public static bool IsElementUnderRectCursor(FrameworkElement element, Point startPoint, Point endPoint, Visual rootVisual)
{
_dCursorTop=Math.Min(startPoint.Y, endPoint.Y);
_dCursorBottom=Math.Max(startPoint.Y, endPoint.Y);
_dCursorLeft=Math.Min(startPoint.X, endPoint.X);
_dCursorRight=Math.Max(startPoint.X, endPoint.X);
FindValues(container, rootVisual);
if (_dContainerTop < _dCursorTop|| _dCursorBottom< _dContainerBottom )
{
return false;
}
if (_dContainerLeft < _dCursorLeft|| _dContainerRight < _dCursorRight)
{
return false;
}
return true;
}
Rootvisual being your window for example;
Then loop over ObservableCollection<FrameworkElement> wells and call that function IsElementUnderRectCursor.
This is inspired from:
Kinecting the Dots
Astreal thanks again for your answer. It's done. I just moved the selection code from modelView to view. The selection done only in the UI.
private void SelectWells(RectangleGeometry selectionRectangle, FrameworkElement frameworkElement)
{
var items = GetItemsControl(frameworkElement);
foreach (var item in items.Items)
{
var viusalItem = (ContentPresenter)items.ItemContainerGenerator.ContainerFromItem(item);
var wellControl = this.GetWellControl(viusalItem);
var relativePoint = wellControl.TransformToAncestor(items).Transform(new Point(0, 0));
var controlRectangle =
new RectangleGeometry(
new Rect(relativePoint.X, relativePoint.Y, wellControl.ActualWidth, wellControl.ActualHeight));
var intersectionGeometry = Geometry.Combine(
selectionRectangle, controlRectangle, GeometryCombineMode.Intersect, null);
if (intersectionGeometry.GetArea() > 0)
{
wellControl.Command.Execute(this);
}
}
}
usefull link for u:
http://www.codeproject.com/Articles/354853/WPF-Organization-Chart-Hierarchy-MVVM-Application
When an user clicks on a node in the tree we need to let the ViewModel node know that the selection has changed. We like to route the event as a command to the ViewModel