Архив рубрики: C

Получение имени метода вызвавшего текущий метод

В C# 6 появилось новое ключевое слово nameof которое позволяет получить строку с именем члена класса, что позволяет существенно упростить написание, например, реализации INotifyPropertyChanged. Зачем он нужен я писал вот здесь. Под катом, я покажу как упростить вызов метода OnPropertyChanged. Ну и покажу какой замечательный атрибут появился в .Net Framework 4.5 благодаря которому, даже nameof больше не нужно.

Итак, немного напомню. У нас есть некоторый класс реализующий упомянутый интерфейс. В интерфейсе объявлено всего одно событие и нам нужно при изменении значений свойств его вызывать. Такой класс может иметь вид:


public class MyClass : INotifyPropertyChanged
{
    private string _myString;

    public string MyString
    {
        get
        {
            return _myString;
        }
        set
        {
            if (value != _myString)
            {
                _myString = value;
                OnPropertyChanged(nameof(MyString));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string p_propertyName)
    {
        PropertyChanged?.Invoke(thisnew PropertyChangedEventArgs(p_propertyName));
    }
}

Как видно, при вызове метода OnProeprtyChanged мы вынуждены передавать строку. Что может привести к тому, что свойство мы переименуем, через рефакторинг изменение распространиться везде кроме этой строки и об изменении свойства никто не узнает.
Решить эту проблему можно применением нового ключевого слова nameof , вот так:


public string MyString
{
    get
    {
        return _myString;
    }
    set
    {
        if (value != _myString)
        {
            _myString = value;
            OnPropertyChanged(nameof(MyString));
        }
    }
}

Но и это еще не все. С появлением атрибута [CallerMemberName], у нас есть возможность вообще ничего не передавать в этот метод, он сам будет узнавать имя члена класса который его вызвал:


public class MyClass : INotifyPropertyChanged
{
    private string _myString;
 
    public string MyString
    {
        get
        {
            return _myString;
        }
        set
        {
            if (value != _myString)
            {
                _myString = value;
                OnPropertyChanged();
            }
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
 
    protected void OnPropertyChanged([CallerMemberNamestring p_propertyName = "")
    {
        PropertyChanged?.Invoke(thisnew PropertyChangedEventArgs(p_propertyName));
    }
}

Стало намного лучше.

Поддержка C# 6.0 на Build Server

Столкнулся с проблемкой, что при создании Build Definition для нового проекта билд падает со странной ошибкой вида:
ExtensionExceptionExtension.cs (22, 0)
Unexpected character '$'
Как выяснилось, на TFS сервере нет студии 2015 и не умеет он билдить C# 6.0.
Под катом как полечить.

1. На машину где установлен билд агент ставим Microsoft Build Tools 2015.
2. На машину где установлен билд агент ставим Microsoft .NET Framework 4.5.2 Developer Pack.
3. Перезагружаем машину где стоит билд агент.
4. В настройках Build Definition указываем атрибут для MSBuild: /tv:14.0

Все работает.


XslCompiledTransform и OutOfMemoryException

Столкнулся с проблемой, что при использовании xslt преобразований возникает Out Of Memory, если входной XML файл имеет достаточно большой объем, а приложение в ресурсах ограниченно, например по тому, что работает в 32-х разрядной системе. Под катом способы, как с этим можно бороться.

1. Отключение отладки

Если вызвать конструктор по умолчанию для XslCompiledTransform, то он автоматически включает режим записи отладочной информации. Во время отладки это может быть и полезно, но вот в работающем приложении это лишняя память. Поэтому надо пользоваться конструктором с булевым параметром и в явном виде ее отключать:

XslCompiledTransform xslt = new XslCompiledTransform(false);

2. Предварительная компиляция XSLT

Как это не странно звучит, но для того чтобы применить XSLT преобразование к XML файлу, файл содержащий это преобразование надо скомпилировать. Можно сэкономить память, если сделать это предварительно ручками.
Для этого, необходимо по пути вида:
C:Program Files (x86)Microsoft SDKsWindowsv8.1AbinNETFX 4.5.1 Tools
найти утилиту: xsltc.exe
и запустить ее указав какой файл с xslt преобразовать, в какую сборку и с каким именем класса записать получившийся код.
Получится что-то вида:
xsltc.exe /class:ClassName /out:OutputXSLT.dll input.xslt
Дальше все просто, подключаем сборку в проект, ну или загружаем ее во время выполнения через Reflection.
Все, теперь вместо пути к файлу, можем указать тип из нашей dll:

XslCompiledTransform xslt = new XslCompiledTransform(false);
xslt.Load(typeof(ClassName));

3. Если приходится преобразовывать много файлов одним XSLT

Во-первых, один раз создав XslCompiledTransform держим его в памяти и не пересоздаем.
Во-вторых, незабываем чистить память под поток записи результатов. Например, так:

using (XmlWriterxmlWriter = XmlWriter.Create(outputFileName))
{
    xslt.Transform(InputXmlFileName, xmlWriter);
}

4. Общее

Ну и последнее, можно попробовать собрать проект в Release конфигурации, может и на этом что-то полуситься выграть по памяти.

О хостинге WCF-сервисов в accoreconsole.exe (AutoCAD 2016)

Как известно, WCF-сервисы могут в качестве хостинга использовать не только IIS и WAS, но так же и произвольные приложения (консольные или GUI). Как показывает практика, в качестве хоста можно использовать acad.exe. В идеале хотелось бы иметь возможность хостить службы в accoreconsole.exe, но не забываем, что это Autodesk, а это означает, что скучать не придётся...


Когда это может оказаться интересным?

Как известно, в параметрах запуска accoreconsole.exe можно указывать набор ключей, в т.ч. и ключ /s, при помощи которого разрешено передавать имя файла скрипта (SCR-файла). По завершению работы скрипта приложение так же автоматически завершит свою работу. Однако порой может возникнуть потребность интерактивного использования функционала, предоставляемого accoreconsole.exe (т.е. само по себе консольное окно при этом не требуется) не закрывая приложение столько времени, сколько потребуется (можно просто скрыть консольное окошко).

Хостинг службы в AutoCAD позволяет другим приложениям (т.н. клиентам) взаимодействовать с ним не прибегая к использованию AutoCAD COM API и при этом получая возможность задействовать возможности AutoCAD .NET API. Кроме того, в любой момент служба может быть перемещена на любой др. компьютер абсолютно прозрачно для клиентов.

В случае необходимости, клиент так же сможет отправлять службе произвольный набор команд, которые будут выполняться в AutoCAD на локальной или удалённо расположенной машине. Например с планшета, работающего под управлением OS Android пользователь сможет отправлять команды пакетной обработки чертежей (очистка, аудит, пересохранение и т.п.).

Это не означает, что на удалённой или локальной машинке обязательно должен быть постоянно запущен AutoCAD. Нет. Клиент может обратиться к службе, которая хостится в IIS или WAS с требованием что-то сделать в AutoCAD. Эта служба запускает AutoCAD (устанавливая видимость его окна в False) и в свою очередь является клиентом для другой службы, хостящейся в AutoCAD, передавая ей ваши запросы, а вам - её ответы. После того, как необходимый вам набор операций в AutoCAD будет выполнен служба, хостящаяся в IIS или WAS завершает работу AutoCAD и ждёт следующих обращений. В случае необходимости, параллельно может быть запущено несколько экземпляров AutoCAD, выполняющих каждый свою задачу, полученную от клиента. Для упрощения в данной теме я не использую промежуточную службу.

Сервис может, к примеру, проверять наличие обновлений (для AutoCAD и его расширений) на сервере компании и в случае их обнаружения сообщать об этом пользователю (или выполнять обновление - это на откуп администраторов CAD).

В идеале, конечно же, лучше всего на роль хоста службы подошёл бы accoreconsole.exe...


Служба

Предположим, что служба, предназначенная для хостинга в AutoCAD, реализует такой интерфейс:

namespace Bushman.CAD.Services {

    [ServiceContract(Namespace = "www.gpsm.ru")]
    interface IMyContract {
        [OperationContract]
        void Write(string msg); // Write message into AutoCAD command console.

        [OperationContract]
        string GetVersion(); // Get AutoCAD version
    }
}

Реализуем обозначенный интерфейс как-то так:

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

namespace Bushman.CAD.Services {
    class MyService : IMyContract {
        public string GetVersion() {
            return cad.Version.ToString();
        }

        public void Write(string msg) {
            Document doc = cad.DocumentManager.MdiActiveDocument;
            if (null != doc) {
                doc.Editor.WriteMessage(msg);
            }
        }
    }

Никаких команд определять не будем, а инициализацию расширения выполним следующим образом:

using System;
using System.ServiceModel;
using cad = Autodesk.AutoCAD.ApplicationServices.Core.Application;
usingAutodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;

[assembly: ExtensionApplication(typeof(Bushman.CAD.Services.ExtensionApplication))]

namespace Bushman.CAD.Services {
    public class ExtensionApplication : IExtensionApplication {

        static ServiceHost host = null;

        static ExtensionApplication() {
            try{
                host = new ServiceHost(typeof(MyService));
                host.Open();
                host.UnknownMessageReceived += Host_UnknownMessageReceived;
                AppDomain.CurrentDomain.ProcessExit += ProcessExit;
            }
            catch(System.Exception ex) {
                Document doc = cad.DocumentManager.MdiActiveDocument;
                if (null != doc) doc.Editor.WriteMessage(ex.Message);
            }
        }

        private static void Host_UnknownMessageReceived(object sender,
            UnknownMessageReceivedEventArgs e) {
            Document doc = cad.DocumentManager.MdiActiveDocument;
            if(null != doc) doc.Editor.WriteMessage(e.Message.ToString());
        }

        private static void ProcessExit(object sender, EventArgs e) {
            if(null != host) host.Close();
        }

        public void Initialize() {
            string status = null == host ? "null" : host.State.ToString();
            Document doc = cad.DocumentManager.MdiActiveDocument;
            if(null != doc) doc.Editor.WriteMessage("\nHost status: {0}.\n", status);
        }

        public void Terminate() {
            // Nothing is here.
        }
    }
}


Клиент

Клиента реализуем следующим образом:

using System;
using System.ServiceModel;
usingBushman.MyClient.ServiceReference1;

namespace Bushman.MyClient {
    classProgram {
        static void Main(string[] args) {
            Console.Title = "CAD client";
            try{
                using (MyContractClient client = new MyContractClient("http")) {
                    if (client.InnerChannel.State != CommunicationState.Faulted) {
                        client.Open();
                        string version = client.GetVersion();
                        Console.WriteLine("CAD version: {0}", version);
                        client.Write("Client said: Hello, AutoCAD.\n");
                    }
                }
            }
            catch(Exception ex) {
                Console.WriteLine(ex.Message);
            }

            Console.WriteLine("Press any key for exit...");
            Console.ReadKey();
        }
    }
}


Конфигурационные файлы acad.exe.config и accoreconsole.exe.config настраиваю на работу с обозначенной выше службой:


<configuration>

  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0"/>
  </startup>

  <!--All assemblies in AutoCAD are fully trusted so there's no point generating publisher evidence-->
  <runtime>
    <generatePublisherEvidence enabled="false"/>
  </runtime>

  <system.serviceModel>
    <services>
      <service name="Bushman.CAD.Services.MyService" behaviorConfiguration="MEXGET">
        <host>
          <baseAddresses>
            <add baseAddress="http://win7x64ac2:8001"/>
          </baseAddresses>
        </host>
        <endpoint name="http"
                  binding="wsHttpBinding"
                  address="MyService"
                  bindingConfiguration="MyContract"
                  contract ="Bushman.CAD.Services.IMyContract">
        </endpoint>
      </service>
    </services>
    <bindings>
      <wsHttpBinding>
        <binding name="MyContract"/>
      </wsHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MEXGET">
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>

    <diagnostics wmiProviderEnabled="true">
      <messageLogging
           logEntireMessage="true"
           logMalformedMessages="true"
           logMessagesAtServiceLevel="true"
           logMessagesAtTransportLevel="true"
           maxMessagesToLog="3000"
       />
    </diagnostics>

  </system.serviceModel>

  <system.diagnostics>
    <sources>
      <source name="System.ServiceModel"
              switchValue="Information, ActivityTracing"
              propagateActivity="true" >
        <listeners>
          <add name="xml"/>
        </listeners>
      </source>
      <source name="System.ServiceModel.MessageLogging">
        <listeners>
          <add name="xml"/>
        </listeners>
      </source>
      <source name="myUserTraceSource"
              switchValue="Information, ActivityTracing">
        <listeners>
          <add name="xml"/>
        </listeners>
      </source>
    </sources>
    <sharedListeners>
      <add name="xml"
           type="System.Diagnostics.XmlWriterTraceListener"
                 initializeData="\\hyprostroy\dfs\Обмен\Бушман\logs\Service-Traces.svclog" />
    </sharedListeners>
  </system.diagnostics>
</configuration>


Конфигурационный файл клиента выглядит так:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
  </startup>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="http" />
      </wsHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://win7x64ac2:8001/MyService" binding="wsHttpBinding"
          bindingConfiguration="http" contract="ServiceReference1.IMyContract"
          name="http">
        <identity>
          <!-- WARNING: change the value according your user principal name. -->
          <userPrincipalName value="admin@hyprostr" />
        </identity>
      </endpoint>
    </client>

    <diagnostics wmiProviderEnabled="true">
      <messageLogging
           logEntireMessage="true"
           logMalformedMessages="true"
           logMessagesAtServiceLevel="true"
           logMessagesAtTransportLevel="true"
           maxMessagesToLog="3000"
       />
    </diagnostics>

  </system.serviceModel>

  <system.diagnostics>
    <sources>
      <source name="System.ServiceModel"
              switchValue="Information, ActivityTracing"
              propagateActivity="true" >
        <listeners>
          <add name="xml"/>
        </listeners>
      </source>
      <source name="System.ServiceModel.MessageLogging">
        <listeners>
          <add name="xml"/>
        </listeners>
      </source>
      <source name="myUserTraceSource"
              switchValue="Information, ActivityTracing">
        <listeners>
          <add name="xml"/>
        </listeners>
      </source>
    </sources>
    <sharedListeners>
      <add name="xml"
           type="System.Diagnostics.XmlWriterTraceListener"
                 initializeData="\\hyprostroy\dfs\Обмен\Бушман\logs\Client-Traces.svclog" />
    </sharedListeners>
  </system.diagnostics>

</configuration>


Запускаем службу и клиента
Служба и клиент могут находится как на одном компьютере, так и на разных компьютерах в сети (или в Интернет). Если в качестве хоста использовать acad.exe, то всё нормально работает:



Но вот если в качестве хоста использовать accoreconsole.exe, то достучаться до сервиса не удастся даже с локального компьютера, на котором запущен хост сервиса. При этом сервис успешно запускается, но не доступен:




В логах клиента можно посмотреть описание проблемы:



По обозначенной теме мне ответил Августо Гонсалес:
Augusto Goncalves (API Evangelist at Autodesk):
As far as I remember trying, accoreconsole.exe doesn't accept new calls (from automation) after is open (but I haven't tried with WCF). Acad.exe is a little different... I don't believe accoreconsole will remain receiving calls after it was launched, it was designed to launch with a list of commands on the .scr file.
Если Августо прав, то это будет очередной, весьма досадный недостаток accoreconsole.exe...

Продолжение темы - здесь...

Блокировка кнопки и контекстного меню закрытия консольного окна

Как известно, accoreconsole.exe всегда был и до сих пор остаётся достаточно кривым... Один из неприятных аспектов его поведения, присутствующий по сей день, заключается в том, что если завершать работу приложения кликом мышки по кнопке закрытия консольного окна в верхнем правом углу, либо выбирая соответствующий пункт из контекстного меню консольного окна, то приложение завершает свою работу через задницу - не выполняя код методов Terminate(), а так же код зарегистрированных событий, таких например, как AppDomain.CurrentDomain.ProcessExit.

Однако обозначенная проблема гораздо глубже и не ограничивается рамками кода ваших расширений: при таком способе закрытия AutoCAD так же не выполняет и свой собственный код, который он обычно выполняет при завершении работы приложения (код корректного освобождения ресурсов, сохранения настроек и т.п.). Например, не происходит восстановление настроек в реестре, которые временно были изменены accoreconsole.exe под свои нужды. Это сразу бросается в глаза на напримере переменной FILEDIA, на время работы консольного приложения устанавливается в 0:  при очередном запуске acad.exe для неё приходится вручную восстанавливать значение 1 (в противном случае вместо диалоговых окон AutoCAD будет использовать свою консоль).

Если завершать работу accoreconsole.exe путём вызова команд quit и exit, то завершение работы приложения происходит так, как это должно было происходить (т.е. выполняется весь необходимый код). Однако никто не застрахован от клика мышкой по обозначенной выше кнопке, а пользователи с вероятностью 100% будут клацать как раз именно по ней, когда потребуется завершить работу приложения, потому как такой способ завершения работы - самый простой.

В качестве "лекарства" против обозначенной выше проблемы я блокирую кнопку закрытия консольного окна и соответствующий её пункт контекстного меню:


  1. const uint MF_BYCOMMAND = 0x00000000;
  2. const uint MF_GRAYED = 0x00000001;
  3. const uint SC_CLOSE = 0xF060;
  4. const uint MF_DISABLED = 0x00000002;
  5. [DllImport("kernel32.dll")]
  6. static extern IntPtr GetConsoleWindow();
  7. [DllImport("user32.dll")]
  8. static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
  9. [DllImport("User32.dll", SetLastError = true)]
  10. static extern uint EnableMenuItem(IntPtr hMenu, uint itemId, uint uEnable);
  11. [DllImport("user32.dll")]
  12. static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags);
  13. ...
  14. // Disable the Close ("X") button and "Close" context menu item of the Console window
  15. IntPtr hwnd = GetConsoleWindow();
  16. IntPtr hmenu = GetSystemMenu(hwnd, false);
  17. uint hWindow = EnableMenuItem(hmenu, SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
  18. // Also it is possible to delete "Close" context menu item
  19. // instead of disabling it.
  20. // DeleteMenu(hmenu, SC_CLOSE, MF_BYCOMMAND);

Однако по факту я вижу, что кнопка закрытия окна заблокирована, а вот контекстное меню - нет... Конечно, можно попросту вовсе удалить этот пункт из контекстного меню и не заморачиваться на эту тему (в обозначенном выше примере кода это успешно делает последняя закомментированная строчка).

Однако мне всё же интересно: почему не блокируется пункт меню?

Оказалось, что обозначенная проблема свойственна не только accoreconsole.exe, но и любому консольному приложению в Windows 7 x64, а так же в Windows Server 2003. А вот в Windows 10 x64 всё работает корректно...

Т.о. то, что контекстное меню не блокируется в некоторых версиях Windows - очень похоже на баг WinAPI.

 В этой же теме сразу размещаю код примера того, как можно скрывать или отображать консольное окно (например всё тот же accoreconsole.exe):
  1. [DllImport("kernel32.dll")]
  2. static extern IntPtr GetConsoleWindow();
  3. [DllImport("user32.dll")]
  4. static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
  5. const int SW_HIDE = 0;
  6. const int SW_SHOW = 5;
  7. IntPtr hwnd = GetConsoleWindow();
  8. // Hide window
  9. ShowWindow(hwnd, SW_HIDE);
  10. // Show window
  11. ShowWindow(hwnd, SW_SHOW);

Длительная, нередко печальная практика показывает, что лозунг Autodesk касательно данного продукта, к сожалению, выглядит как-то так:
`accoreconsole.exe` - мы заставим Вас работать через задницу!

C#: скачиваем файлы из Интернет


Пример кода для простого скачивания файлов из Интернет (например файла http://stroustrup.com/Programming/PPP2code/std_lib_facilities.h). Сетевые настройки при этом считываются из настроек Internet Explorer.




static voidMain(string[] args) {
    String file_name = Path.GetRandomFileName();
    String full_path = Environment.ExpandEnvironmentVariables(
        Path.Combine(@"%LocalAppData%Temp", file_name));

    using (WebClientclient = new WebClient()) {
        String uri = "http://stroustrup.com/Programming/PPP2code/std_lib_facilities.h";
        var proxyUri = WebRequest.GetSystemWebProxy()
            .GetProxy(new Uri(uri));
        client.Proxy = new WebProxy(proxyUri);
        client.Proxy.Credentials = CredentialCache.DefaultCredentials;
        try{
            client.DownloadFile(uri, full_path);
        }
        catch(Exception ex) {
            Console.WriteLine(ex.Message);
        }
    }
    Console.WriteLine("The result file: {0}", full_path);
    Console.WriteLine("Press any key for exit.");
    Console.ReadKey();
}



Об изменениях в новой книге Стровструпа "Практика программирования"

Из типографии вышел перевод очередной версии книги "Программирование. Принципы и практика использования C++", написанной Беарне Стровструпом - создателем языка C++. Поскольку на сайте автора я не нашёл информации об изменениях в новой книге, то сравнил оригинальные содержания предыдущего и нового издания этой книги.

Далее приведён перечень изменений в содержании новой книги.

+ 4.6.1        Traversing a vector
+ 8.5.9        constexpr functions
+ 9.5.1        "Plain" enumerations
+ 15.3.3    Lambda expressions
+ 16.3.3    A lambda expressions as a callback
+ 18.2        Initialization
+ 18.3.4    Moving
+ 19.3.3    Concepts
- 19.5.4    auto_ptr
+ 19.5.4    unique_ptr
+ 19.5.5    Return by moving
+ 20.5.1    Container traversal
+ 20.5.2    auto
+ 21.4.3    Lambda expressions
+ 21.9        Container algorithms
+ 23.6.1    Raw string literals
- 26.3.5    Testing classes
- B.6.3        pair
+ B.6.3        pair and tuple
+ B.6.4     initializer_list
+ B.6.5        Resource management pointers
+ B.9.6        Random numbers
+ B.10        Time

DETAILED_ERROR — расширенная информация об ошибке

Выложил на Bitbucket исходный код функции detailed_error и макроса DETAILED_ERROR, использующего её. Они позволяют сгенерировать исключение runtime_exception с нужным сообщением об ошибке, к которому автоматически добавляется информация, позволяющая понять - где именно в исходном коде произошло исключение. Наличие информации о дате компиляции позволяет узнать, какой commit проекта следует смотреть, дабы получить нужную версию файла исходного кода. Предоставляемая информация о разрядности приложения и о версии компилятора так же может быть полезной в ряде случаев.

Как я отвечаю на вопросы

Эта публикация из серии ответы на вопросы с MSDN. Вот только на вопрос я уже ответил, но вот в топике был задан вопрос: "А как вы даете ответы?". Вопрос переформулирован мной, по ссылке он в полном виде можно сходить и посмотреть. Под катом я расскажу как я отвечал конкретно на оригинальный вопрос из топика и вообще как я действую столкнувшись с интересной проблемой.


Поехали.
Проблема была в том, что есть Rectangle. К нему применено несколько трансформаций. Одна из трансформаций RotateTransform на некоторый угол. Как, например, при попадании мышки на Rectangle узнать угол поворота. Вот так выглядел вопрос (картинка кликабельна):
Т.к. из текста вопроса проблема не понятна, то создал пустой проект, скопировал в него XAML и код чтобы посмотреть что происходит. У меня монитор меньше 21000 пикселей, поэтому прямоугольник после запуска приложения я не увидел. Пришлось менять размеры и положение.
Когда же прямоугольник стал виден, то добавил в метод точку останова:
Навел мышку, начал отладку по шагам и увидел, что RenderTransform содержит экземпляр класса TransformGroup и, само собой, при приведении к типу RotateTransform будет null:
Дальше все просто. Посмотрел, что свойство RenderTransform типа Transform, а у него нет ни Items, ни Children. Смотрел просто, донаборщиком:

Ну а раз нет, то значит придется явно приводить к типу TransformGroup. У которого есть свойство Children.
Дальше все просто, по XAML видно, что в Children есть три трансформации, т.к. нам нужна трансформация известного типа, то применяем из Linq метод OfType (пользуюсь им достаточно часто, поэтому даже сомнений не было что здесь использовать). Ну а дальше все просто. По конкретному примеру все.

В общем случае, когда хочу что-то сделать, то уже сразу, исходя из предыдущего опыта, есть несколько идей как это сделать. Дальше смотрю какие есть методы, классы что-то через MSDN, что-то просто в донаборщике иногда переходя по F12 к сигнатуре. Отладчик очень часто подсказывает где какой тип, тоже можно воспользоваться. Ну а если все равно не получается или выскакивает какая-нибудь заковыристая ошибка, то ищу поисковиками. Очень часто решение находится на MSDN, Stackoverflow или в блогах. Ответ в большинстве случаев будет на английском, поэтому решив проблему, бывает, пишу статью в этот бложик, вдруг кому пригодится. Совсем редко бывают ситуации когда все уперся, не могу ничего путного найти/придумать. Ок, тогда пытаюсь реализовать другой способ решения. В описанной выше задаче, это могло быть хранение углов трансформации во ViewModel или Model с прокидыванием во View через Binding. При этом подходе с визуальными компонентами вообще дела можно не иметь, т.к. все значения у меня уже доступны бизнес-логике приложения.

Создан cribs.red-bee.ru

Закинул на red-bee.ru несколько страниц с заметками, которые могут оказаться интересными при разработке различного рода административных утилит. Опубликованы здесь. Материал не претендует на абсолютную истину (не исключено, что какие-то задачи можно было решить и более простым путём).