How to get AutoCAD to show updated information in a block attribute reference without using SetFocusToDwgView?

How to get AutoCAD to show updated information in a block attribute reference without using SetFocusToDwgView?

richardpangburn
Enthusiast Enthusiast
921 Views
5 Replies
Message 1 of 6

How to get AutoCAD to show updated information in a block attribute reference without using SetFocusToDwgView?

richardpangburn
Enthusiast
Enthusiast

I have a WPF application in which I have implemented the MVVM pattern. I have a window that has a textbox bound to a string property that in a round about way is populated by the user pressing a button which brings up a promptentity.

 

That's kind of besides the point. I populate the text box with the value of the selected block attribute reference, and the user is allowed to modify it in any way that they like, then they press another button to apply their changes back to the block attribute reference. This runs another method that passes the new value into the block and voila it works.


When writing this code I realized that the attribute value, or I guess the state of the AutoCAD drawing itself, won't change unless the user clicks back focus into AutoCAD from my WPF window, and then the change "kicks in" and becomes visible.

 

I decided to make this a seamless process so at the end of my method that updates the block attribute reference I call 

Autodesk.AutoCAD.Internal.Utils.SetFocusToDwgView();

 From the user's perspective the update is seamless.

 

So that's all fine and dandy but then I wanted to get fancy and thought, hey I want to make it so that when my user is typing in the textbox on my application, AutoCAD is updating in real-time. This allows them to see the changes that they are making in terms of spacing and new lines and all that as it's rather important to know that stuff.

 

I implemented something from expression blend into my TextBox that looks like this:

 

<i:Interaction.Triggers>
                    <i:EventTrigger
        EventName="TextChanged">
                        <i:InvokeCommandAction
            Command="{Binding UpdateAttributeValuesCommand}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>

 The idea now is that every time the text changes in the text box (from the user typing in stuff) the command to update the block attribute will run and they should see the block changing in real time. This of course does not work when the code also tells AutoCAD to steal focus. And if I don't tell AutoCAD to steal focus, the user can type all they want, but the change won't be reflected in the drawing until they click over in AutoCAD to bring it back into focus, which defeats the entire purpose of what I'm trying to do.

 

So my question is, is there any way I can get AutoCAD to update it's state without having to give it focus or steal focus away from my application?

0 Likes
Accepted solutions (1)
922 Views
5 Replies
Replies (5)
Message 2 of 6

richardpangburn
Enthusiast
Enthusiast

I think I passed the limit of how long a post can sit before I can edit so I'm adding this post here to simplify my question:

 

I'm making an application that is a modeless window that changes the value of block attributes. However, looking at those blocks in my drawing, the changes that I make when I run my "Apply" command don't visibly appear on the block until I change focus back to AutoCAD. When I do that, the changes "pop in" instantly.

 

How can I get changes that I make to the document with that window to visibly apply in AutoCAD without switching focus back to AutoCAD?

0 Likes
Message 3 of 6

_gile
Consultant
Consultant

You can try Editor.UpdateScreen().

 

XAML

<UserControl x:Class="BlockAttributeFromPalette.TabView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="150" d:DesignWidth="200">
    <Grid Background="WhiteSmoke">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Margin="10" Width="80" Command="{Binding GetCommand}">Get</Button>
        <TextBox Grid.Row="1" Margin="10" Text="{Binding AttValue}" />
        <Button Grid.Row="2" Margin="10" Width="80" Command="{Binding SetCommand}">Set</Button>
    </Grid>
</UserControl>

 

ViewModel

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;

using System.ComponentModel;

using AcAp = Autodesk.AutoCAD.ApplicationServices.Core.Application;

namespace BlockAttributeFromPalette
{
    public class TabViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        string attValue = "";
        ObjectId attId;

        public TabViewModel()
        {
            GetCommand = new RelayCommand((_) => GetAttValue(), (_) => true);
            SetCommand = new RelayCommand(
                (_) => SetAttValue(),
                (_) => !attId.IsNull && attId.Database == AcAp.DocumentManager.MdiActiveDocument?.Database);
        }

        public string AttValue
        {
            get { return attValue; }
            set { attValue = value; RaisePropertyChanged(nameof(AttValue)); }
        }

        public RelayCommand GetCommand { get; }

        public RelayCommand SetCommand { get; }

        public void GetAttValue()
        {
            var doc = AcAp.DocumentManager.MdiActiveDocument;
            if (doc == null) return;
            var db = doc.Database;
            var ed = doc.Editor;
            PromptNestedEntityResult pner;
            while (true)
            {
                pner = ed.GetNestedEntity("\nSelect Attribute: ");
                if (pner.Status != PromptStatus.OK)
                    return;
                if (pner.ObjectId.ObjectClass == RXObject.GetClass(typeof(AttributeReference)))
                    break;
                ed.WriteMessage("\nSelected object is not an attribute.");
            }
            attId = pner.ObjectId;
            using (var tr = db.TransactionManager.StartTransaction())
            {
                var att = (AttributeReference)tr.GetObject(attId, OpenMode.ForRead);
                AttValue = att.TextString;
                tr.Commit();
            }
        }

        public void SetAttValue()
        {
            var doc = AcAp.DocumentManager.MdiActiveDocument;
            if (doc == null) return;
            var db = doc.Database;
            using (doc.LockDocument())
            using (var tr = db.TransactionManager.StartTransaction())
            {
                var att = (AttributeReference)tr.GetObject(attId, OpenMode.ForWrite);
                att.TextString = attValue;
                tr.Commit();
                doc.Editor.UpdateScreen();
            }
        }
    }
}


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 4 of 6

_gile
Consultant
Consultant
Accepted solution

Sorry, I didn't read your article carefully enough.
This version should be closer to your request.

 

XAML

 

<UserControl x:Class="BlockAttributeFromPalette.TabView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="150" d:DesignWidth="200">
    <Grid Background="WhiteSmoke">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Margin="10" Width="80" Command="{Binding GetCommand}">Get Attribute</Button>
        <TextBox Grid.Row="1" Margin="10" Text="{Binding AttValue, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding TextBoxEnabled}" />
    </Grid>
</UserControl>

 

 

C#

 

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;

using AcAp = Autodesk.AutoCAD.ApplicationServices.Core.Application;

using System.ComponentModel;
using Autodesk.AutoCAD.ApplicationServices;

namespace BlockAttributeFromPalette
{
    public class TabViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        string attValue = "";
        ObjectId attId;
        bool textBoxEnabled;

        public TabViewModel()
        {
            GetCommand = new RelayCommand((_) => GetAttValue(), (_) => true);
            AcAp.DocumentManager.DocumentActivated += DocumentManager_DocumentActivated;
        }

        private void DocumentManager_DocumentActivated(object sender, DocumentCollectionEventArgs e)
        {
            TextBoxEnabled = !attId.IsNull && e.Document?.Database == attId.Database;
        }

        public string AttValue
        {
            get { return attValue; }
            set
            {
                attValue = value;
                SetAttValue();
                RaisePropertyChanged(nameof(AttValue));
            }
        }

        public bool TextBoxEnabled
        {
            get { return textBoxEnabled; }
            set
            {
                textBoxEnabled = value;
                RaisePropertyChanged(nameof(TextBoxEnabled));
            }
        }

        public RelayCommand GetCommand { get; }

        private void GetAttValue()
        {
            var doc = AcAp.DocumentManager.MdiActiveDocument;
            if (doc == null) return;
            PromptNestedEntityResult pner;
            while (true)
            {
                pner = doc.Editor.GetNestedEntity("\nSelect Attribute: ");
                if (pner.Status != PromptStatus.OK)
                    return;
                if (pner.ObjectId.ObjectClass == RXObject.GetClass(typeof(AttributeReference)))
                    break;
                doc.Editor.WriteMessage("\nSelected object is not an attribute.");
            }
            attId = pner.ObjectId;
            using (var tr = new OpenCloseTransaction())
            {
                var att = (AttributeReference)tr.GetObject(attId, OpenMode.ForRead);
                AttValue = att.TextString;
                tr.Commit();
            }
            TextBoxEnabled = true;
        }

        private void SetAttValue()
        {
            var doc = AcAp.DocumentManager.MdiActiveDocument;
            if (doc == null)
                return;
            using (doc.LockDocument())
            using (var tr = doc.TransactionManager.StartTransaction())
            {
                var att = (AttributeReference)tr.GetObject(attId, OpenMode.ForWrite);
                att.TextString = attValue;
                tr.Commit();
                doc.Editor.UpdateScreen();
            }
        }
    }
}

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 5 of 6

_gile
Consultant
Consultant

A little screencast.

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 6 of 6

richardpangburn
Enthusiast
Enthusiast

Wow that screencast thing is awesome. I'm going to have to remember to use it the next time I am trying to ask a question. Thanks Gile. UpdateScreen() was exactly what I needed. I implemented it into my application and it worked like a dream. I feel like I've been blessed by the patron saint of AutoCAD development...

0 Likes