gpu blog


WPF:Размер окна в зависимости от текущего монитора

Дано
WPF-Приложение и комп с 2-мя мониторами с разным разрешением (e.g. 2560х1440) и (1920х1080).

Задача
В зависимости от текущего монитора использовать максимально доступную область для главного окна приложения.

К сожалению получение информации о подключенных мониторах доступно для Windows Forms и не доступно для WPF.
При невозможности использования Windows Forms можно использовать p-invoke для доступа к системным функциям User32.dll.

Решение на VB.Net
Определяем необxодимые нам структуры (RectStructure, MonitorInfo)
<StructLayout(LayoutKind.Sequential)>
Public Structure RectStructure
    Public Left As Integer
    Public Top As Integer
    Public Right As Integer
    Public Bottom As Integer
End Structure

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
Public Structure MonitorInfo
    Public Size As Integer
    Public Monitor As RectStructure
    Public WorkArea As RectStructure
    Public Flags As UInteger
End Structure

И внешние методы>
<DllImport("user32.dll")>
Private Shared Function GetWindowRect(ByVal windowHandle As IntPtr, ByRef rectangle As RectStructure) As Boolean
End Function

<DllImport("user32.dll")>
Private Shared Function MonitorFromRect(<[In]> ByRef rectPointer As RectStructure, ByVal flags As UInteger) As IntPtr
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto)>
Private Shared Function GetMonitorInfo(ByVal hmon As IntPtr, ByRef mi As MonitorInfo) As Boolean
End Function

Пишем вспомогательный метод для получения хендл монитора из хендла окна>
Private Const MONITOR_DEFAULTTONEAREST As UInteger = 2
Private Shared Function GetMonitorHandleByWindow(ByVal windowHandle As IntPtr) As IntPtr
    Dim rect As New RectStructure()
    GetWindowRect(windowHandle, rect)
    Dim monitorHandle = MonitorFromRect(rect, MONITOR_DEFAULTTONEAREST)
    Return monitorHandle
End Function

И собственно функцию которая будет нами использована в приложении для установки размера и положения окна>
Public Shared Function SetWindowDimensions(window As Window) As Boolean
    Dim ret As Boolean = False
    If window.IsActive Then
        Dim wih As New WindowInteropHelper(window)
        Dim winHandle As IntPtr = wih.Handle
        If winHandle <> IntPtr.Zero Then
            Dim monitorHandle As IntPtr = GetMonitorHandleByWindow(winHandle)
            Dim mi = New MonitorInfo()
            mi.Size = CUInt(Marshal.SizeOf(mi))
            GetMonitorInfo(monitorHandle, mi)
            window.Height = mi.WorkArea.bottom - mi.WorkArea.Top
            window.Width = mi.WorkArea.Right - mi.WorkArea.Left
            window.Left = mi.WorkArea.Left
            window.Top = mi.WorkArea.Top
            ret = True
        End If
    End If
    Return ret
End Function

Как мы видим размер и положение окна будет производиться только при условии что окно активировано и возможно получить хендла окна из обькта window.
Опытным путем было выяснено что очень важно вызвать SetWindowDimensions в правильном месте в правильное время. В зависимости от настроек системы и того
на каком монитире мы стартуем наше приложение , мы можем получить не тот монитор на котором приложение окончательно будет отображено.
Например, мы стартуем приложение на втором мониторе и второй монитор будет корректно возвращаться нам как текущий монитор при>
- Инициализации проложения (Application_Startup)
- Главное окно приложения загружено (Loaded)
- Главное окно приложения активировано (OnActivated)

Но после этого при дальнейшей инициализации (загрузка подконтролов) внезапно получим первый монитор как текущий вместо ожидаемого второго.
Так что выбор места для вызова SetWindowDimensions очень важен, по крайней мере в моем случае ето было именно так.
Возможно ето не всегда так и SetWindowDimensions спокойно можно вызывать скажем когда главное окно приложения активировано.
добавлено: 25 апр 18 просмотры: 1584, комментарии: 3



i18n (Интернационализация) в WPF и Silverlight. Числовые поля

Введение.
Представим себе что у нас есть число 1234,56 которое надо отобразить в нашем приложении в поле ввода или в гриде.
На компьютере с региональными настройками Америки, число должно отобразиться так:
1,234.56
На компьютере с региональными настройками Германии, число должно отобразиться так:
1.234,56

Проблема заключается в том что внутренние механизмы WPF и Silverlight не совсем правильно используют региональные настройки.


В интернете можно найти достаточно много решений, например:
Silverlight and Application Globalization – The Converter Approach
К сожалению данный вариант не позволяет решить проблему полностью.

Поскольку мне не удалось "выгуглить" метод, который бы позволял решить данную проблему в комплексе, предлагаю свой вариант решения для данной проблемы.

Решение для ВПФ.

1. Переопределениe мета информации у FrameworkElement.
        FrameworkElement.LanguageProperty.OverrideMetadata(GetType(FrameworkElement), New FrameworkPropertyMetadata(Markup.XmlLanguage.GetLanguage(Globalization.CultureInfo.CurrentCulture.IetfLanguageTag)))

Ето можно сделать в классе Application
    Protected Overrides Sub OnStartup(e As Windows.StartupEventArgs)
        FrameworkElement.LanguageProperty.OverrideMetadata(GetType(FrameworkElement), New FrameworkPropertyMetadata(Markup.XmlLanguage.GetLanguage(Globalization.CultureInfo.CurrentCulture.IetfLanguageTag)))
        MyBase.OnStartup(e)
    End Sub


2. Переопределение Binding
Используя MarkupExtensions создаем свой CustomBinding унаследованный от Binding и принудительно устанавливаем ConverterCulture в CultureInfo.CurrentCulture
В xaml-e используем CustomBinding вместо стандартного Binding там где ето оправдано, т.е. числовые поля.


3. Конвертер
Код конвертера достаточно тривиален, например:
    Public Class DoubleConverter
        Implements IValueConverter
        Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
            Dim dv As Double = CDbl(value)
            Dim frmtString As String = "N2"
            If parameter IsNot Nothing Then
                If TryCast(parameter, String) IsNot Nothing Then
                    frmtString = CStr(parameter)
                End If
            End If
            Return dv.ToString(frmtString, culture)
        End Function

        Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
            Dim ret As Double
            Dim stringValue As String = CStr(value)
            Dim style As NumberStyles = NumberStyles.Number
            Double.TryParse(stringValue, style, culture, ret)
            Return ret
        End Function
    End Class



И даже если мы поменяем региональные настройки.
Например:
десятичный разделитель заменить на #
тысячный на ?

То увидим что число 1234,56 будет отображаться как
1#234?56

И наоборот, если пользователь введет
2#987?01
то мы получим корректное число
2987,01


Решение для Silverlight

Решение аналогично с ВПФ, единственное метод FrameworkElement.LanguageProperty.OverrideMetadata нам не доступен
и CustomBinding наследуем от MarkupExtensions.
добавлено: 25 июн 14 просмотры: 1198, комментарии: 0



Создание много модульных сборок. Часть 2

В предыдущей части ([url=]http://www.sql.ru/blogs/gpu_blog/1804[/url]) рассматривалось решение с доступом к исходникам.
Данная запись рассматривает решение без доступа к исходникам


Решение 2 (Наличие сборок)
Возникла необходимость создание .NET Assembly состоящей из нескольких модулей.
Используемые инструменты:

Компилятор (vbc.exe) и Ассемблер (ilasm.exe) находяться в
C:\Windows\Microsoft.NET\Framework\v4.0.30319
Линкер (al.exe) и дизассемблер (ildasm.exe) находяться в>
"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools"

Исходные данные
Сборка Assembly1.dll, содержащая один класс Class1.
Сборка Assembly2.dll, содержащая один класс Class2.

Конечное приложение> App.vbproj
Imports Assembly1
Imports Assembly2
Class Application
    Public Sub New()
        Dim c1 As New Class1()
        c1.method()
        Dim c2 As New Class2()
        c2.method()
    End Sub
End Class

Конечное приложение имeет ссылки на Assembly1.dll и Assembly2.dll

Необходимо заменить ссылки на Assembly1.dll и Assembly2.dll на единственную ссылку, назовем ее AssemblyGate.dll
без изменения исходного кода ( Imports Assembly1)


1. Получаем IL Код
ildasm.exe Assembly1.dll /output:References\Assembly1.dll.il
ildasm.exe Assembly2.dll /output:References\Assembly2.dll.il

Cодержимое Assembly1.dll.il будет выглядеть как то так
+ IL Код

.assembly Assembly1
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )

// --- The following custom attribute is added automatically, do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 )

.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.
.custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 1A 2E 4E 45 54 46 72 61 6D 65 77 6F 72 6B // ....NETFramework
2C 56 65 72 73 69 6F 6E 3D 76 34 2E 30 01 00 54 // ,Version=v4.0..T
0E 14 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C // ..FrameworkDispl
61 79 4E 61 6D 65 10 2E 4E 45 54 20 46 72 61 6D // ayName..NET Fram
65 77 6F 72 6B 20 34 ) // ework 4
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module Assembly1.dll
// MVID: {78BF4A04-E722-4E9D-BEE6-C00A5F2D8D4E}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0002 // WINDOWS_GUI
.corflags 0x00000001 // ILONLY
// Image base: 0x00660000


// =============== CLASS MEMBERS DECLARATION ===================
.class public auto ansi Assembly1.Class1
extends [mscorlib]System.Object
{
} // end of class Assembly1.Class1

// =============================================================

// *********** DISASSEMBLY COMPLETE ***********************


2. Редактирование IL кода
Удаляем секцию .assembly из IL кода и сохраняем изменения

3. Пересобираем Assembly1.dll и Assembly2.dll
ilasm.exe /output:References\Assembly1.dll /dll References\Assembly1.dll.il
ilasm.exe /output:References\Assembly2.dll /dll References\Assembly2.dll.il

4. Создаем Сборку
al.exe" /target:lib /out:References\AssemblyGate.dll References\Assembly1.dll References\Assembly2.dll

В директории References будут находиться 3 файла
Assembly1.dll (бинарный модуль 1)
Assembly2.dll (бинарный модуль 2)
AssemblyGate.dll (многомодульная сборка)

5. Изменяем конечное приложение
Удаляем проектные ссылки на сборки Assembly1 и Assembly2 и добавляем ссылку на AssemblyGate.dll
Компилируем проект.
Файлы Assembly1.dll, Assembly2.dll и AssemblyGate.dll будит скопированы в исполняемую директорию проекта.
Запускаем приложение и убеждаемся что все работает.
добавлено: 10 апр 14 просмотры: 935, комментарии: 0



Создание много модульных сборок. Часть 1

Возникла необходимость создание .NET Assembly состоящей из нескольких модулей.
Используемые инструменты:

Компилятор (vbc.exe) и Ассемблер (ilasm.exe) находяться в
C:\Windows\Microsoft.NET\Framework\v4.0.30319
Линкер (al.exe) и дизассемблер (ildasm.exe) находяться в>
"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools"

Исходные данные
Проект Assembly1.vbproj (Root namespace Assembly1) состоящий из одного файла Class1.
Проект Assembly2.vbproj (Root namespace Assembly2) состоящий из одного файла Class2.

Конечное приложение> App.vbproj
Imports Assembly1
Imports Assembly2
Class Application
    Public Sub New()
        Dim c1 As New Class1()
        c1.method()
        Dim c2 As New Class2()
        c2.method()
    End Sub
End Class

Конечное приложение имeет ссылки на Assembly1.dll и Assembly2.dll

Необходимо заменить ссылки на Assembly1.dll и Assembly2.dll на единственную ссылку, назовем ее AssemblyGate.dll
без изменения исходного кода ( Imports Assembly1)

Решение 1 (Имеем доступ к исходникам)
1. Создаем модули
vbc.exe /t:module Assembly1\Class1.vb /out:References\Assembly1.dll
vbc.exe /t:module Assembly2\Class2.vb /out:References\Assembly2.dll

2. Создаем Сборку
al.exe" /target:lib /out:References\AssemblyGate.dll References\Assembly1.dll References\Assembly2.dll

В директории References будут находиться 3 файла
Assembly1.dll (бинарный модуль 1)
Assembly2.dll (бинарный модуль 2)
AssemblyGate.dll (многомодульная сборка)

Посмотрим на содержимое AssemblyGate.dll, для етого используем дизассемблер
ildasm.exe References\AssemblyGate.dll /output:References\1\AssemblyGate.dll.il
+ IL Код

---------------------------------------------------------------------------------
.file Assembly1.dll
.hash = (E7 06 DA 08 00 9B 70 69 90 02 0A E9 90 F3 67 90 // ......pi......g.
17 90 69 91 ) // ..i.
.file Assembly2.dll
.hash = (3D D2 8F D8 AA C4 B9 7B AC 28 94 CF F0 D0 B1 72 // =......{.(.....r
34 CB 1C 3C ) // 4..<
.class extern public Assembly1.Class1
{
.file Assembly1.dll
.class 0x02000002
}
.class extern public Assembly2.Class2
{
.file Assembly2.dll
.class 0x02000002
}
.module AssemblyGate.dll
// MVID: {D6FE70F4-4DC7-47D8-A2B6-7C7D0691A71A}
---------------------------------------------------------------------------------

Как видим сборка содержит внешние ссылки на классы, находящиеся в модулях Assembly1 и Assembly2

3. Изменяем конечное приложение
Удаляем проектные ссылки на сборки Assembly1 и Assembly2 и добавляем ссылку на AssemblyGate.dll
Компилируем проект.
Файлы Assembly1.dll, Assembly2.dll и AssemblyGate.dll будит скопированы в исполняемую директорию проекта.
Запускаем приложение и убеждаемся что все работает.
добавлено: 10 апр 14 просмотры: 859, комментарии: 0