Welcome to MSDN Blogs Sign in | Join | Help

Silverlight 3 is out!

“Well, unless you’ve been living under a rock…” © CyrusN :)

I’m very excited about Silverlight 3 which has TONS of new awesome features, from better graphics and text to out-of-browser support, style improvements and… drumroll… SaveFileDialog (finally) :)

Links:

http://silverlight.live.com

http://silverlight.live.com (Microsoft Silverlight Streaming) is a free service from Microsoft to host your Silverlight applications and videos. You can upload your .xap or .wmv files and reference them from any websites, blogs, etc. My favorite part is that you get 10 GB in free storage, with no ads, no strings attached. Also, they give you high scalability and availability – the media is hosted “in the cloud” that can handle a lot of traffic. Finally, you have a nice admin site where you can manage your account, videos and applications – you can upload new videos, applications, track usage (number of downloads or video views), etc.

The MSDN Silverlight Streaming Samples page contains several nice Silverlight samples. I use http://silverlight.live.com to host http://livegeometry.com and my other applications and videos. Enjoy!

Samples for the Undo Framework

I just added some samples for the Undo Framework. You can find the samples in the source code or download them from the project website.

MinimalSample

First is a simple Console App sample called MinimalSample. Here’s the full source code:

using System;
using GuiLabs.Undo;

namespace MinimalSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Original color");

            SetConsoleColor(ConsoleColor.Green);
            Console.WriteLine("New color");

            actionManager.Undo();
            Console.WriteLine("Old color again");

            using (Transaction.Create(actionManager))
            {
                SetConsoleColor(ConsoleColor.Red); // you never see Red
                Console.WriteLine("Still didn't change to Red because of lazy evaluation");
                SetConsoleColor(ConsoleColor.Blue);
            }
            Console.WriteLine("Changed two colors at once");

            actionManager.Undo();
            Console.WriteLine("Back to original");

            actionManager.Redo();
            Console.WriteLine("Blue again");
            Console.ReadKey();
        }

        static void SetConsoleColor(ConsoleColor color)
        {
            SetConsoleColorAction action = new SetConsoleColorAction(color);
            actionManager.RecordAction(action);
        }

        static ActionManager actionManager = new ActionManager();
    }

    class SetConsoleColorAction : AbstractAction
    {
        public SetConsoleColorAction(ConsoleColor newColor)
        {
            color = newColor;
        }

        ConsoleColor color;
        ConsoleColor oldColor;

        protected override void ExecuteCore()
        {
            oldColor = Console.ForegroundColor;
            Console.ForegroundColor = color;
        }

        protected override void UnExecuteCore()
        {
            Console.ForegroundColor = oldColor;
        }
    }
}

Here we define a new action called SetConsoleColorAction, and override two abstract methods: ExecuteCore() and UnExecuteCore(). In the ExecuteCore(), we change the color to the new color and backup the old color. In UnExecuteCore() we rollback to the backed up old color. We pass the action all the context information it needs (in our case, the desired new color). We rely on the action to backup the old color and store it internally.

The philosophy is to only store the smallest diff as possible. Try to avoid copying the entire world if you can only save the minimal delta between states.

Next, pay attention to the SetConsoleColor method. It wraps creating the action and calling RecordAction on it. It helps to create an API that abstracts away the action instantiation so that it is transparent for your callers. You don’t want your callers to create actions themselves every time, you just want them to call a simple intuitive API. Also, if for whatever reason you’d like to change or remove the Undo handling in the future, you’re free to do so without breaking the clients.

Finally, the source code in Main shows how you can intersperse your API calls with calls to Undo and Redo. It also shows using a transaction to group a set of actions into a single “multi-action” (Composite design pattern). You can call your API within the using statement, but the actions’ execution is delayed until you commit the transaction (at the end of the using block). That’s why you don’t see the console color changing to red in the middle of the block. If you undo a transaction, it will undo all the little actions inside it in reverse order.

WinFormsSample

The second sample is called WinFormsSample. It shows a windows form that let’s you edit properties of a business object:

image

You can change the text of both name and age, and the values will be mapped to the business object. You can also “Set Both Properties” which illustrates transactions. Then you can click Undo and it will rollback the state of your object to the previous state. The UI will update accordingly.

There is a trick in the code to avoid infinite recursion: on textbox text change, update the business object, fire an event, update the textboxes, update the business object again, etc... We use a boolean flag called “reentrancyGuard” that only enables the TextChanged events if the textbox modification was made by user, and not programmatically. If we updated the textboxes as a results of the business object change, no need to update the business object.

Note: If this was WPF, I would just use two-way data binding, but I wanted to keep the sample as simple as possible and use only basic concepts.

Action merging

Another thing worth mentioning that this sample demonstrates is action merging. As you type in the name in the textbox ‘J’, ‘o’, ‘e’, you don’t want three separate actions to be recorded, so that you don’t have to click undo three times. To enable this, an action can determine if it wants to be merged with the next incoming action. If the next incoming action is similar in type to the last action recorded in the buffer, they merge into a single action that has the original state of the first action and the final state of the new action. This feature is very useful for recording continuous user input such as mouse dragging, typing and other events incoming at a high rate that you want to record as just one change.

We update the visual state of the Undo and Redo buttons (enabled or disabled) to determine if the actionManager can Undo() or Redo() at the moment. We call the CanRedo() and CanUndo() APIs for this.

Hopefully this has been helpful and do let me know if you have any questions.

Posted by Kirill Osenkov | 5 Comments
Filed under: ,

New CodePlex project: a simple Undo/Redo framework

I just created a new CodePlex project: http://undo.codeplex.com

What

It's a simple framework to add Undo/Redo functionality to your applications, based on the classical Command design pattern. It supports merging actions, nested transactions, delayed execution (execution on top-level transaction commit) and possible non-linear undo history (where you can have a choice of multiple actions to redo).

The status of the project is Stable (released). I might add more stuff to it later, but right now it fully satisfies my needs. It's implemented in C# 3.0 (Visual Studio 2008) and I can build it for both desktop and Silverlight. The release has both binaries.

Existing Undo/Redo implementations

I do realize that my project is the reinvention of the wheel at its purest, existing implementations being most notably:

However I already have three projects that essentially share the exact same source code, so I decided that it would be good to at least extract this code into a reusable component, so perhaps not only me but someone else might find it useful too.

It's open-source and on CodePlex, so I also have a chance of benefiting from it if someone contributes to it :)

History

It all started in 2003 when I first added Undo/Redo support to the application that I was developing at that time. I followed the classical Command design pattern, together with Composite (for nested transactions) and Strategy (for plugging various, possibly non-linear undo buffers).

Then I needed Undo/Redo for my thesis, so I just took the source code and improved it a little bit. Then I started the Live Geometry project, took the same code and improved it there a little bit, fixing a couple of bugs. Now the mess is over, and I'm finally putting the code in one place :)

A good example of where this framework is used is the Live Geometry project (http://livegeometry.codeplex.com). It defines several actions such as AddFigureAction, RemoveFigureAction, MoveAction and SetPropertyAction.

Actions

Every action encapsulates a change to your domain model. The process of preparing the action is explicitly separated from executing it. The execution of an action might come at a much later stage after it's been prepared and scheduled.

Any action implements IAction and essentially provides two methods: one for actually doing the stuff, and another for undoing it.

/// <summary>
/// Encapsulates a user action (actually two actions: Do and Undo)
/// Can be anything.
/// You can give your implementation any information it needs to be able to
/// execute and rollback what it needs.
/// </summary>
public interface IAction
{
    /// <summary>
    /// Apply changes encapsulated by this object.
    /// </summary>
    void Execute();

    /// <summary>
    /// Undo changes made by a previous Execute call.
    /// </summary>
    void UnExecute();

    /// <summary>
    /// For most Actions, CanExecute is true when ExecuteCount = 0 (not yet executed)
    /// and false when ExecuteCount = 1 (already executed once)
    /// </summary>
    /// <returns>true if an encapsulated action can be applied</returns>
    bool CanExecute();

    /// <returns>true if an action was already executed and can be undone</returns>
    bool CanUnExecute();

    /// <summary>
    /// Attempts to take a new incoming action and instead of recording that one
    /// as a new action, just modify the current one so that it's summary effect is 
    /// a combination of both.
    /// </summary>
    /// <param name="followingAction"></param>
    /// <returns>true if the action agreed to merge, false if we want the followingAction
    /// to be tracked separately</returns>
    bool TryToMerge(IAction followingAction);

    /// <summary>
    /// Defines if the action can be merged with the previous one in the Undo buffer
    /// This is useful for long chains of consecutive operations of the same type,
    /// e.g. dragging something or typing some text
    /// </summary>
    bool AllowToMergeWithPrevious { get; set; }
}

Both methods share the same data required by the action implementation and are supplied when you create an action instance.

ActionManager

Your domain model (business objects) will likely have an instance of ActionManager that keeps track of the undo/redo buffer and provides the RecordAction(IAction) method. This method adds an action to the buffer and executes it. And then you have ActionManager.Undo(), ActionManager.Redo(), CanUndo(), CanRedo() and some more stuff.

As a rule, the thing that works for me is that I generally have two APIs: one that is public and lazy (i.e. it just creates an action and adds it to the buffer), and the other which is internal and eager, that does the actual work. Action implementation just calls into the eager API, while the public API is lazy and creates actions transparently for the consumer.

History

Right now I only have a SimpleHistory. Instead of having two stacks, I have a state machine, where Undo goes to the previous state and Redo goes to the next state, if available. Each graph edge stores an action (implementation of IAction). As the current state transitions along the graph edge, IAction.Execute or UnExecute is being called, depending on the direction in which we go (there is a logical "left" and "right" in this graph, which kind of represents "future" and "past").

 

image

It's possible for this linked list to become a tree, where you try something out (way1), don't like it, undo, try something else (way2), like it even less, undo, and choose to go back and redo way1. However this is not implemented yet.

Transactions

Transactions are groups of actions viewed as a single action (see Composite design pattern).

Here's a typical usage of a transaction:

public void Add(IEnumerable<IFigure> figures)
{
    using (Transaction.Create(ActionManager))
    {
        figures.ForEach(Add);
    }
}

If an action is recorded while a transaction is open (inside the using statement), it will be added to the transaction and executed only when the top-level transaction commits. This effectively delays all the lazy public API calls in the using statement until the transaction commits. You can specify that the actions are not delayed, but executed immediately - there is a corresponding overload of Transaction.Create specifically for that purpose.

Note that you can "misuse" this framework for purposes other than Undo/Redo: one prominent example is navigation with back/forward.

Update: I just posted some samples for the Undo Framework: http://blogs.msdn.com/kirillosenkov/archive/2009/07/02/samples-for-the-undo-framework.aspx

Visual Studio 2010 Beta1 + TFS + HTTPS (TF31001): The ServicePointManager does not support proxies with the https scheme.

This is just a little note to myself and others who might run into this. I was using Visual Studio 2010 and Team Foundation Client to access a CodePlex project over HTTPS (port 443), and got this error message:

---------------------------
Microsoft Visual Studio
---------------------------
Microsoft Visual Studio

TF31001: Cannot connect to Team Foundation Server at tfs07.codeplex.com. The server returned the following error: The ServicePointManager does not support proxies with the https scheme.
---------------------------
OK   Help  
---------------------------

By the way, did you know that you can press Ctrl+C to copy the contents of a message box dialog to clipboard? (Well, at least in Visual Studio message boxes).

Anyway, it turns out this is a known bug: https://connect.microsoft.com/VisualStudio/feedback/Workaround.aspx?FeedbackID=453677

The workaround so far is to create a couple of string values in the registry:

This problem it seams is to do with the way Visual Studio 2010 connects to your TFS server over HTTPS. The default value for “BypassProxyOnLocal” in Visual Studio 2008 was “False”, but it has been changed to “True” for Visual Studio 2010 Beta 1.

You can fix this by adding the following registry keys and restarting Visual Studio 2010:

You need to add a “RequestSettings” key to both of the following location that contains a string value pair of “BypassProxyOnLocal=’False’”.

32bit OS Key Locations:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\TeamFoundationServer\10.0\RequestSettings
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\10.0\TeamFoundation\RequestSettings

64bit key locations:
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\TeamFoundationServer\10.0\RequestSettings
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0\TeamFoundation\RequestSettings

How to: Change the BypassProxyOnLocal Configuration: http://msdn.microsoft.com/en-us/library/bb909716(loband).aspx

Update: I just noticed Aaron Block has also blogged about this.

Posted by Kirill Osenkov | 1 Comments
Filed under:

VS Project, C++ and Editor team blogs

This is just a quick announcement about some Visual Studio team blogs that might be really worth checking out.

Posted by Kirill Osenkov | 0 Comments
Filed under:

Algorithms in C#: shortest path around a polygon (polyline routing)

Suppose you have to build a road to connect two cities on different sides of a lake. How would you plan the road to make it as short as possible?

To simplify the problem statement, a lake is sufficiently well modeled by a polygon, and the cities are just two points. The polygon does not have self-intersections and the endpoints are both outside the polygon. If you have Silverlight installed, you can use drag and drop on the points below to experiment:

Solution description

A shortest path between two points is a segment that connects them. It’s clear that our route consists of segments (if a part of the path was a curve other than a segment, we could straighten it and get better results). Moreover, those segments (which are part of the route) have their endpoints either on polygon vertices or the start or end point. Again, if this were not the case, we would be able to make the path shorter by routing via the nearest polygon vertex.

Armed with this knowledge, let’s consider all possible segments that connect the start and end point and all polygon vertices that don’t intersect the polygon. Let’s then construct a graph out of these segments. Now we can use Dijkstra’s algorithm (or any other path finding algorithm such as A*) to find the shortest route in the graph between start and endpoints. Note how any shortest path algorithm can essentially boil down to a path finding in a graph, because a graph is a very good representation for a lot of situations.

From the implementation perspective, I used my dynamic geometry library and Silverlight to create a simple demo project that lets you drag the start and end points as well as polygon vertices. You can also drag the polygon and the plane itself. I also added rounded corners to the resulting path and made it avoid polygon vertices to make it look better.

Here is the source code for the sample. Here’s the main algorithm. It defines a data structure to describe a Graph that provides the ShortestPath method, which is the actual implementation of the Dijkstra’s algorithm. ConstructGraph takes care of adding all possible edges to the graph that do not intersect our polygon. SegmentIntersectsPolygon also determines what the name suggests.

I hope to post more about polygon routing in the future and do let me know if you have any questions.

yield return and Continuation-Passing Style

Someone was recently porting some C# code to VB and had a question about how to convert the C# yield return iterator methods to VB (VB currently doesn’t support iterators).

There were a lot of replies like “use Reflector on a compiled binary and copy-paste the generated state machine code”. The problem with the Reflector approach is that you go one step down the abstraction ladder and lose the high-level intent expressed in the original code. Resulting code will be surely harder to read and maintain.

Surprisingly, no one mentioned CPS. But before applying continuation-passing style, let’s first look at the nature of yield return. It’s essentially a producer-consumer model where the producer is a state machine where transitions are triggered by the MoveNext calls and current state is saved in the Current property. On the consumer side there is eventually almost always a foreach loop with some logic in the body, and this logic only requests the next element (and triggers a state machine transition) after it’s done processing the current element.

It turns out, we can preserve the algorithm encoded in the iterator and avoid using yield return and thus having the compiler to generate the state machine code for us. To achieve this, we pass the logic that used to be in the consumer foreach loop (a continuation) directly into the iterator.

Here’s an example with yield return that we want to convert:

using System;
using System.Collections.Generic;

class Node<T>
{
    public Node<T> Left { get; set; }
    public T Value { get; set; }
    public Node<T> Right { get; set; }

    public IEnumerable<T> Traverse()
    {
        if (Left != null)
        {
            foreach (var item in Left.Traverse())
            {
                yield return item;
            }
        }
        yield return Value;
        if (Right != null)
        {
            foreach (var item in Right.Traverse())
            {
                yield return item;
            }
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Node<int> tree = new Node<int>()
        {
            Left = new Node<int>()
            {
                Value = 1,
                Right = new Node<int>()
                {
                    Value = 2
                }
            },
            Value = 3,
            Right = new Node<int>()
            {
                Value = 4
            }
        };

        foreach (var item in tree.Traverse())
        {
            Console.WriteLine(item);
        }
    }
}

Now, the trick is to pass the “continuation”, which is the code that processes the results, directly into the iterator method (using first-class functions AKA delegates):

public IEnumerable<T> Traverse()
{
    List<T> result = new List<T>();
    TraverseInner(this, result.Add);
    return result;
}

void TraverseInner(Node<T> root, Action<T> collector)
{
    if (root == null) return;
    TraverseInner(root.Left, collector);
    collector(root.Value);
    TraverseInner(root.Right, collector);
}

Note how we created an internal helper that actually does the traversing and how the logic of the traversal is even shorter than in the original method. We don’t use yield return and still maintain a high level of abstraction. Where was yield return, now is a call to the helper method. Otherwise, the control flow is the same.

The downside of this approach though is that we lose laziness, which means, that once requested, we eagerly calculate all the results and return them at once. This is the price that we pay for losing the state machine that can store intermediate results for us.

If we remove the limitation of having to return IEnumerable<T>, we can directly consume the helper method without having to write a foreach loop:

TraverseInner(tree, Console.WriteLine);

Here we’re passing the “continuation” (which is the Console.WriteLine method) directly inside the iterator. Note how the consumer side became shorter as well because we don’t have to write a foreach loop.

Note: a while ago I blogged about yield foreach which would allow to get rid of foreach statements in the iterator scenario as well.

Note 2: I’m guessing it’s possible to get rid of yield return and still keep the laziness, I just need to do more homework on Push LINQ and similar to find a nice solution to this.

Posted by Kirill Osenkov | 5 Comments
Filed under: ,

Some resources about Visual Studio Extensibility

A couple of readers have posted questions about Visual Studio Extensibility, DTE, your own packages, commands, experimental hive etc. To be frank, I’m not an expert in this field, so instead of trying to answer these questions, I will point to some better resources for VSX (VS Extensibility):

Posted by Kirill Osenkov | 1 Comments
Filed under:

Should Call Hierarchy display compiler-generated member calls?

[A quick reminder, Call Hierarchy is a new IDE feature in VS 2010]

In the comments to the previous post, a reader is asking:

But why are the query operator method calls ignored by the Call Hierarchy?

var r = from i in new[] { 1, 2, 3 }

       where i > 1

       select i;

The code snippet above calls Enumerable.Where and Enumerable.Select, and I reacon they should go into Call Hierarchy, which is not the case of the current beta. Any hint on this?

This is actually a good question. We thought about this issue in the feature design meetings and the reasoning behind the decision not to show the LINQ methods was as follows. The foreach statement also calls GetEnumerator and MoveNext, lock calls Monitor.TryEnter (or something similar??), not to mention other places where compiler-generated code calls members (e.g. the yield return state machine, lambdas, etc). The question is simple: where do we stop? In other words, how deep do we go down the abstraction ladder?

This also applies to things like calling += on events, which actually calls Delegate.Combine, calling a delegate calls the Invoke method, etc. etc. We decided that we will only show a member call in Call Hierarchy if the compiler actually sees the member’s symbol in the source code. Find All References also follows this pattern, e.g. if you look for Point, you will have two references in Point p = new Point(); and only one reference in var p = new Point(); – since the symbol doesn’t show up in your source code, we don’t mention it. This might be misleading actually, also when you’re looking for calls to a method, we won’t show you places where you create a delegate that points to this method (or method group).

Do you think this reasoning is OK or would you like us to change the feature’s behavior? If yes, how exactly should it behave? Also keep in mind that changing this behavior at this point will be pretty costly (i.e. I will have to retest everything!). Not even to mention that our dev will have to change the implementation :)

Thanks!

Posted by Kirill Osenkov | 3 Comments
Filed under: ,

Visual Studio 2010 Beta 1 is out!

In case you missed it (which I don’t believe), today we released Visual Studio 2010 Beta 1 to MSDN subscribers. On Wednesday, it will become available for everyone else to download.

The build is much more stable and fleshed out then the early CTP preview – I’d say, the functionality of Visual Studio 2010 is at least 95% there already. Our next top priority is fixing performance and making VS fast, sleek and snappy. But it already got its new WPF “skin”, the brand-new WPF editor, historical debugger, architecture explorer, and of course, C# 4.0 and VB 10 new language and IDE features. Also, for the first time ever, F# is in the box just like C# and VB.

In any case – go try it out and tell us what you think. If you have anything to say about the new C# Call Hierarchy toolwindow – let me know.

Posted by Kirill Osenkov | 2 Comments
Filed under: ,

A simple sample for C# 4.0 ‘dynamic’ feature

Earlier I posted some code to start Visual Studio using C# 3.0:

using System;
using EnvDTE;

class Program
{
    static void Main(string[] args)
    {
        Type visualStudioType = Type.GetTypeFromProgID("VisualStudio.DTE.9.0");
        DTE dte = Activator.CreateInstance(visualStudioType) as DTE;
        dte.MainWindow.Visible = true;
    }
}

Now here’s the code that does the same in C# 4.0:

using System;

class Program
{
    static void Main(string[] args)
    {
        Type visualStudioType = Type.GetTypeFromProgID("VisualStudio.DTE.10.0");
        dynamic dte = Activator.CreateInstance(visualStudioType);
        dte.MainWindow.Visible = true;
    }
}

At first, it looks the same, but:

  1. Referencing EnvDTE.dll is not required anymore – you also don’t need using EnvDTE – don’t need to reference anything!
  2. You declare the ‘dte’ variable to be weakly typed with the new dynamic contextual keyword
  3. You don’t have to cast the instance returned by Activator.CreateInstance
  4. You don’t get IntelliSense as you type in the last line
  5. The calls to dte are resolved and dispatched at runtime

It’s a trade-off, but I still view dynamic as yet another useful tool in the rich C# programmer’s toolbox to choose from.

Posted by Kirill Osenkov | 11 Comments
Filed under: , ,

Jon Skeet: Planning C# In Depth 2

Jon is asking advice about how to shape the second edition of his book “C# In Depth”. Jon, I like your current suggestions (in blue) about making tweaks to existing content and adding a new section on C# 4.0.

One thing I’d also love to see is a summary of the hypothetical C# vNext features that the community has been discussing in the blogosphere and on the forums. I think Jon is the right person to come up with a great summary of those features, the rationale behind them, and possible implementation (syntax).

Specifically, I’m talking about:

  1. Immutability (e.g. public readonly class) and related matters (initializing auto-props, using tuples as an implementation for anonymous types, making object initializers immutable, tuples in the language etc.). Also statically verifying immutability etc.
  2. yield foreach and allowing anonymous methods to be iterators (like a VB prototype that Paul Vick blogged about)
  3. return type covariance
  4. duck typing, making a type implement an interface without touching it’s source, techniques (TransparentProxy, Reflection.Emit etc)
  5. member-level var (hopefully we won’t have this)
  6. notnull, code contracts in the language
  7. traits/mixins (e.g. redirecting an interface’s implementation to a field/property that implements that interface)
  8. metaprogramming (syntactic/semantic macros, DSLs in the language, infoof, compile-time reflection, IL rewriting (PostSharp, CCI, Mono.Cecil), AOP, transformations, code generation)
  9. parallel programming, concurrency, etc.
  10. Am I missing something?

Common Compiler Infrastructure released on CodePlex

Great news – Herman Venter from Microsoft Research has released CCI (Common Compiler Infrastructure) on CodePlex: http://ccimetadata.codeplex.com. See Herman’s blog post here.

The Microsoft Research Common Compiler Infrastructure (CCI) is a set of components (libraries) that provide some of the functionality that compilers and related programming tools tend to have in common.

The metadata components provide functionality for reading, writing and manipulating Microsoft Common Language Runtime (CLR) assemblies and debug files. The functionality provided by these components subsumes the functionality provided by System.Reflection and System.Reflection.Emit.

It’s interesting to know that FxCop is actually powered by CCI, so if you wondered how FxCop analyzes your assemblies, you can now peek into the source code and even build your own tools that leverage the CCI framework.

Update:

Beside the metadata rewriting engine and IL/PDB/PE framework, CCI also provides the AST and syntax trees to model source code, IL <-> CodeModel roundtripping, C# pretty-printer, as well as SmallBasic compiler as a sample.

See here:

Posted by Kirill Osenkov | 2 Comments
Filed under:

Remote Desktop: /span across multiple monitors

I spent some time searching the web about Remote Desktop, fullscreen and multiple monitors, so I decided to write down my findings to avoid having to search for them again.

/span for multiple monitors

If you pass /span to mstsc.exe, the target session’s desktop will become a huge rectangle that equals to the summary area of your physical monitors. This way the remote desktop window will fill all of your screens. The downside of this approach is that both screens are part of one desktop on the remote machine, so if you maximize a window there, it will span all of your monitors. Also, a dialog that is centered, will show up right on the border between your monitors. There is software on the web to workaround that but I’m fine with keeping my windows restored and sizing them myself. Also Tile Vertically works just fine in this case.

Saving the /span option in the .rdp file

There is a hidden option that isn’t mentioned in the description of the .rdp format:

span monitors:i:1

Just add it at the bottom of the file.

Saving the /f (fullscreen) option in the .rdp file

screen mode id:i:2

(By default it’s screen mode id:i:1, which is windowed).

Sources

Posted by Kirill Osenkov | 4 Comments
Filed under:
More Posts Next page »
 
Page view tracker