GSI

What’s new in WPF 3.5

WPF 2007. 9. 7. 09:29 |

What’s new in WPF 3.5

With the release of WPF 3.5 beta 2 (download here: http://www.microsoft.com/downloads/details.aspx?FamilyId=D2F74873-C796-4E60-91C8-F0EF809B09EE&displaylang=en), we’ve added some exciting new features to WPF 3D.  At a very high level these can be grouped in to two main additions: UIElement3D and Viewport2DVisual3D.  Over the next couple of weeks we’ll be adding examples and tips and tricks to the blog on these new additions, but for now we’ll start with a quick overview of what both of these provide.

UIElement3D

In the 2D world, UIElement adds layout, input, focus and eventing on to Visual.  UIElement3D brings these same things (except no layout) to 3D.  What this means is that the standard events and means of adding event handlers that you’re used to with UIElements now applies in the 3D world with UIElement3D. 

UIElement3D itself is an abstract class that derives from Visual3D.  To make it useable out of the box without having to derive from UIElement3D yourself, we’ve provided two new classes:  ModelUIElement3D and ContainerUIElement3D. 

ContainerUIElement3D does exactly what its name says.  It is a container for other Visual3Ds.  It has one main property, Children, which is used to add and remove 3D children.  The ContainerUIElement3D doesn’t have a visual representation itself, but rather is just a collection of other 3D objects.

ModelUIElement3D has one property, Model, which is the Model3D that should be displayed to represent the UIElement3D.  It has no children itself, and in some ways you can think of ModelUIElement3D like a Shape in the 2D world.

If you’re familiar with ModelVisual3D, then ContainerUIElement3D and ModelUIElement3D should look very familiar.  The difference is we’ve gone and split the functionality of ModelVisual3D (i.e. a model and children) in to two separate classes, one with a model and the other with children.

With these then, making use of layout, focus and eventing is very easy.  For instance, say you want to create a 3D object that responds to mouse events, you can just do:

                <ModelUIElement3D  MouseDown="OnMouseDown"/>

And then in the code behind have:

      protected void OnMouseDown(object sender, MouseButtonEventArgs e)

      {

          Console.WriteLine("Hello");

      }

If you have experience adding event handlers in the 2D world, then this should look immediately familiar.  In fact it’s exactly the same, and UIElement3D is going to handle the same routed events that a UIElement deals with.  With the addition of UIElement3D you now get all the great functionality that UIElement provided to 2D, but now in the 3D world!

Viewport2DVisual3D

The second main addition is Viewport2DVisual3D which enables you to put interactive 2D on 3D in WPF.  If you’ve used the 3DTools work that was released shortly after the 3.0 release of WPF, Viewport2DVisual3D will look very familiar to you.  With 3.5 though we’ve fully integrated 2D on 3D in to WPF rather than requiring a separate DLL to have this feature.  The 2D and 3D trees are also integrated now, so if you walk up to a 2D visual on 3D and ask it for its parent, it will tell you the 3D object it is on.

Viewport2DVisual3D derives from Visual3D and has three main dependency properties: Visual, Geometry and Material.

Visual – This is the 2D Visual that will be placed on the 3D object.

Geometry – The 3D geometry for the Viewport2DVisual3D

Material – This describes the look of the 3D object.  You can use any material you want.  For the material that you want to have the Visual be placed on, you simply need to set the Viewport2DVisual3D.IsVisualHostMaterial attached property to true.

Below is a XAML example using Viewport2DVisual3D

<Viewport2DVisual3D Geometry="{StaticResource plane}">

        <Viewport2DVisual3D.Material>

          <DiffuseMaterial  Viewport2DVisual3D.IsVisualHostMaterial="true" />

        </Viewport2DVisual3D.Material>

       

        <Button>3.5!</Button>          

      </Viewport2DVisual3D>

The above is a very quick overview of what is added in 3.5, so expect more posts on more specific details as well as some code samples on how to use it.

-Kurt Berglund

Posted by gsi
:

Of course I couldn't mention a technique of possible real-time cell-shading yesterday without actually trying it out. No, I didn't attempt to derive from BitmapEffect. (I'd love to take a crack at BitmapEffect but I simply cannot justify the expenditure of time.)

Instead, I tried the RenderTargetBitmap approach. Here's a rough outline:

  • Lay out your Page or Window normally but instead of defining a Viewport3D element, use a Border instead. The Border is perhaps the simplest FrameworkElement derivative that has a Background property. Give the Background property an object of type ImageBrush.
  • Whatever you would have put into your Viewport3D put into a Viewport3DVisual instead. Set the Viewport property of the Viewport3DVisual to the actual size of the Border element. You'll need a handler for the SizeChanged event of the Border to keep this Viewport property updated when the Border size changes.
  • In that same SizeChanged event handler for the Border, create an object of type RenderTargetBitmap the same size as the Border and the Viewport3DVisual. You'll be recreating this bitmap whenever the size of the Border changes.
  • Insteall event handlers for anything else (such as scrollbars) that affect the 3D scene defined within the Viewport3DVisual.
  • Whenever anything happens that affects the appearance of the Viewport3DVisual perform the following actions:
    • Clear the RenderTargetBitmap by calling the Clear method.
    • Render the Viewport3DVisual on the bitmap by calling the Render method.
    • Dump the pixel bits of the RenderTargetBitmap into an array by calling CopyPixels.
    • Do whatever processing you want on the pixel bits. (For cell-shading, I just AND-ed each 32-bit ARGB pixel with 0xFFC0C0C0.)
    • Create a new bitmap based on the altered pixel bits by calling BitmapSource.Create.
    • Set that new bitmap to the ImageSource property of the ImageBrush object set to the Background property of the Border.

Here's the RealTimeCellShading source code. Sorry I can't give you an XBAP, but apparently Clear and CopyPixels are prohibited methods for partial trust. (But Render and BitmapSource.Create are mysteriously OK. Hey — I don't make up the XBAP rules!)

I've used an AmbientLight at 1/4 white and DirectionalLight of 3/4 white with a direction of (2, –3 –1). I've provided a pair of scrollbars to rotate the camera relative to the teapot, and a pair of sliders to rotate the teapot relative to the camera and light sources. Here's a view with the teapot slightly rotated:

Posted by gsi
:

사용자가 지정한 여러가지 정보들을 ListBox에 표현하는 방법을 기술합니다.
하나의 사용자 정보에는 여러개의 정보가 포함되어 있으며,
그 정보를 ObservableCollection 와 같은 Collection의 객체로 리스트화 합니다.

본 내용은 WPF Application을 기초로 합니다.

** 사용자 데이터 구성**

public class Place
{
    private string _name;

    private string _state;

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public string State
    {
        get { return _state; }
        set { _state = value; }
    }

    public Place(string name, string state)
    {
        this._name = name;
        this._state = state;
    }

    public override string ToString()
    {
        //return base.ToString();
        return _name.ToString();
    }
}

public class Places : ObservableCollection<Place>
{
    public Places()
    {
        Add(new Place("Bellevue", "WA"));
        Add(new Place("Gold Beach", "OR"));
        Add(new Place("Kirkland", "WA"));
        Add(new Place("Los Angeles", "CA"));
        Add(new Place("Portland", "ME"));
        Add(new Place("Portland", "OR"));
        Add(new Place("Redmond", "WA"));
        Add(new Place("San Diego", "CA"));
        Add(new Place("San Francisco", "CA"));
        Add(new Place("San Jose", "CA"));
        Add(new Place("Seattle", "WA"));
    }
}


**xaml에서 사용방법**
1. 네임 스페이스 추가
xmlns:c="clr-namespace:WpfApplication1"

2. Window.Resources에 클래스 등록
<Window.Resources>
        <c:Places x:Key="PlacesData" />
        ................
</Window.Resources>


3. 화면 구성
<StackPanel>
      <TextBlock FontSize="18" Margin="5" FontWeight="Bold"
                 HorizontalAlignment="Center">Data Trigger Sample</TextBlock>
      <ListBox Width="180" HorizontalAlignment="Center" Background="Honeydew"
                 ItemsSource="{Binding Source={StaticResource PlacesData}}"/>
</StackPanel>


아래와 같은 화면으로 구성이 됩니다.

사용자 삽입 이미지


위의 내용들을 보시면 하나같이 WpfApplication1.Place 라고 나옵니다.
이것은 Collection 내부에 있는 오브젝트의 값을 ToString를 통해서 기본 값을 가져 오기 때문인데요(말이 좀 그렇다^^) 여기서 ToString()를 오버라이트 해서 _name의 값을 리턴 하도록 해보겠습니다.

Place 클래스 내부에 ToString()를 추가합니다.
public override string ToString()
{
     return base.ToString();
}

추가를 하게 되면 위와 같은 코드가 자동으로 추가가 됩니다.

여기서 return base.ToString()return _name.ToString()로 바꾸어 주면
위의 내용이 아니라 이름으로된 결과물을 보실수 있을겁니다.

이후에는 Place에 있는 정보들을 다 보여 주는 템플릿을 구성할 수 있습니다. ^^

본 예제는 msdn 로컬 파일주소를 참조 하세요.
ms-help://MS.MSDNQTR.v90.en/wpf_conceptual/html/0f4d9f8c-0230-4013-bd7b-e8e7fed01b4a.htm
(물론 웹에도 같은게 있습니다. 주소는 직접 찾기 바랍니다. ^^..)
Posted by gsi
:

본 내용은 msdn에 있는
How to: Control When the TextBox Text Updates the Sources 에 대한 내용을
조금 나름대로 이해 할 수 있도록 정리한 것입니다.
영어는 잘 몰라서 번역 수준은 아닙니다.


TextBox의 Text는 기본적으로 UpdateSourceTrigger 의 값이 LostFocus로 되어 있다고 합니다. 그래서 Text를 다 적고 나서 마우스나 기타 다른 것을 통해서 Focus가 다른 데로 갔을때 업데이트가 되도록 되어 있습니다.

하지만 이 것을 Text를 입력 하고 있는 중간에 계속 해서 업데이트가 가능하게 할려면
UpdateSourceTrigger를 수정해줘야 하는거 같습니다.

즉, TextBox 의 Binding 속성을 사용해서 UpdateSourceTrigger의 값을 PropertyChanged로 수정해주어야 되는거 같네요.

블렌드에서 TextBox와 TextBlock을 사용해서 값을 Data Binding 해서 테스트를 해보면
값이 바뀌는것을 볼 수 있습니다.

하지만 지금 예제는 cs 파일을 통해서 연동하는 부분을 살펴 보게 되겠습니다.

Person.cs 파일을 하나 생성하게 되는데요.

1. 네임 스페이스 지정
using System.ComponentModel;

2. INotifyPropertyChanged를 상속 받는 클래스 생성
class Person : INotifyPropertyChanged
{
}


3. 데이터 값으로 name를 생성
private string name;

4. PropertyChangedEventHandler 이벤트 핸들 생성
public event PropertyChangedEventHandler PropertyChanged;

5. name를 사용할 인터페이스(??)를 생성, get, set 두개 지정
public string PersonName
{
    get { return name; }
    set
    {
        name = value;

        OnPropertyChanged("PersonName");
    }
}


6. PropertyChanged 를 처리할 함수 생성
protected void OnPropertyChanged(string name)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        handler(this, new PropertyChangedEventArgs(name));
    }
}

cs 파일을 만든 다음에 xaml을 처리 합니다.
1. Person.cs 파일을 사용하기 위해서 네임 스페이스를 지정
xmlns:src="clr-namespace:UntitledProject1"

2. Window.Resources 에 Person의 키를 생성
<src:Person x:Key="myDataSource" PersonName="Joe"/>
 - x:Key="myDataSource" 를 하게 되면 Person의 생성자가 호출되는거 같네요.
 - PersonName="Joe" 를 하게 되면 public string PersonName 의 set 함수가
호출되는거 같습니다.

3. TextBox의 Text 속성에 바인딩 시킵니다.
<TextBox.Text>
    <Binding Source="{StaticResource myDataSource}" Path="PersonName"
                   UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
 - Source에는 Window.Resource에 Person을 선언해 놓은 myDataSource를 적용합니다. Path에는 PersonName를 추가 하게 되는데요. PersonName는 Person.cs 파일에 존재 하는 것입니다. (만약 xml을 사용하게 된다면 Path가 아니라 XPath가 되며 "PersonName"이 아니라 @"PersonName"가 됩니다.)
 - UpdateSourceTrigger="PropertyChanged"를 셋팅해 주게 됩니다.

4. TextBlock 의 Text 속성에 바인딩 시킵니다.
<TextBlock Text="{Binding Source={StaticResource myDataSource}, Path=PersonName}"/>
 - UpdateSourceTrigger 는 지정하지 않고 TextBox와 동일하게 구성합니다.
 - 위의 코드는 아래와 같이도 쓸수 있겠네요.
<TextBlock>
    <TextBlock.Text>
        <Binding Source="{StaticResource myDataSource}" Path="PersonName"/>
    </TextBlock.Text>
</TextBlock>


-----------------------------------------------------
작동 흐름을 대충 찍어 보면..

1. <src:Person x:Key="myDataSource" PersonName="Joe"/>
여기를 거치게 되면서
- public Person() 생성자가 호출 됩니다.
- public string PersonName 의 set에 value로 "Joe"가 입력 됩니다.
- OnPropertyChanged("PersonName"); 가 호출됩니다.
- OnPropertyChanged 내부에서는 handler가 null 이라서 그냥 빠져 나가네요.

2. Path="PersonName"
- 이 구문을 만나게 되면서 public string PersonName 이 호출되고 여기에서 name의 값을 get으로 리턴 시켜 줍니다.

3. TextBox의 Text에 내용 입니다.
- public string PersonName 의 set 가 호출 됩니다.
- OnPropertyChanged("PersonName"); 를 호출합니다.
- OnPropertyChanged() 내부에서 handler가 null이 아니게 되며, handler()를 호출하게 됩니다.
- public string PersonName 가 호출 되게 되는데요.. 음 아무래도 여기서, xaml의 코드를 거쳐서 TextBlock의 Text의 값이 변하게 되는거 같습니다.

**이상 간단하게 봤습니다.**

WPF를 하면서 C#의 스킬 부족과 영어 울렁증, xaml의 코드 난해함에.. 많이 헤매게 되는거 같습니다. 빨리 익혀서 이것저것 만들어 보는 날이 왔으면 좋겠네요 ^^.

강좌가 조금 난잡해 진거 같은데요.. ^^..
본 강좌의 소스는 인터넷의 msdn 사이트나 msdn을 설치 하셨다면 아래 주소에 있습니다.^^
ms-help://MS.MSDNQTR.v90.en/wpf_samples/html/b2906631-8a4b-43b4-b077-9e732a8ff363.htm (로컬msdn)

잘못된 부분이나 조금 도움이 될만한 내용이 있으시다면 적어 주시면 고맙겠습니다. ^^

Posted by gsi
:

아래의 코드는 내가 정신없이 한줄을 작성하면서 생긴 일이다.
참으로 어이가 없는 경우이긴 하지만 발생할 수 있는 상태가 된 나로서는
아무래도 헤롱헤롱 하는 상태인듯 하다.

d:\NG_Project\Project\NGTools\NGMakerShop080\NGMakerShop080_Work\DataMgr\NGBObject.h(70): error C2039: 'first' : is not a member of 'std::_Tree<_Traits>::iterator'
        with
        [
            _Traits=std::_Tmap_traits<int,int,std::less<int>,std::allocator<std::pair<const int,int>>,false>
        ]

문제가된 코드

  map< int, int> m_test;
  m_test[10] = 10;
  map< int, int>::iterator iter = m_test.begin();
  int aaa = iter.first; //<--여기..

이 코드를 보고도 어디가 문제인지 모르는 분은 나랑 비슷한 상태인듯 하다 ^^

이 코드는 아래와 같이 되어야 한다.

int aaa = (*iter).first;

앞으로는 조금더 정신 차리고 해야 할거 같다.

Posted by gsi
: